Feeds:
Posts
Comments

Posts Tagged ‘xtext’

On Jul 28th we released a first version of our Xtext Unit Testing utilities. It supports makes unit-testing of Xtext-based grammars a no-brainer!!

Integration-style testing of files

image

A integration-style test covers many of the aspects you want to test, when writing new (test-first), or changing grammars (regression).

A simple test like this one:

@Test
public void person_no_attributes(){
    testFile("person_no_attributes.dmodel");
}

; will cover following functionality:

  • Model file parsed into EMF-model with no errors
  • All cross-references resolved
  • Validation passed; no warnings, no errors.
  • Serializer runs without errors
  • Formatter runs without errors
  • Serialize+Format exactly matches the input-files’ content

Further capabilities (need documentation!)

  • Fluent-API for validation assertion. Example:
// error line 1: person.name-feature is missing display name
assertConstraints(
  issues.errorsOnly()
             .inLine(1)
             .under(Modul.class, "person")
             .named("name")
             .oneOfThemContains("missing display name")
);
  • Switches for turning on/off serialization and formatter

Unit-testing of Parser- or Lexer-Rules

I’ll just show an example here:

@Test
public void id(){
    testTerminal("bar", /* token stream: */ "ID");
    testTerminal("bar3", /* token stream: */ "ID");
    
    testNotTerminal("3bar", /* unexpected */ "ID");
    
    // token streams with multiple token
    testTerminal("foo.bar", "ID", "'.'", "ID");
}

@Test
public void qualifiedNameWithWildcard(){
    testParserRule("foo.*", "QualifiedNameWithWildCard");
}

Download / Install

The Eclipse plugin is available on this P2 update site: http://xtext-utils.eclipselabs.org.codespot.com/git.distribution/releases/unittesting-0.9.x

The project is hosted on Google Code:

Source: http://code.google.com/a/eclipselabs.org/p/xtext-utils/source/checkout?repo=unittesting

Unit-Testing Documentation: Unit_Testing - xtext-utils - Unit testing - A collection of useful utilities and samples for Xtext based projects - Google Project Hosting

Read Full Post »

Xtext is great (textual DSLs)

In my last project I worked on a external textual DSL for defining products in telecommunications.

At this time, the products and their parts (deep tree of “features” then composed to products) are all maintained in Excel. This is great for the “inventor” and power-user, but it’s very hard to enforce structural correctness and business rules. Also working in a team and versioning is almost impossible.

A textual DSL was chosen over a custom application, because gives much more “power” to the user, because he is editing raw data in a very feasible way.

Sad that Microsoft dropped their investment in DSLs. But, so what. We’ve got Xtext. There is only one drawback. It’s Java + Eclipse, not C# + Visual Studio.

First I had plans (and started) to write about a particular scoping problem I had to solve in this DSL, but then I moved to a different project. So instead I’ll just post the introductory part where I listed the features of Xtext, just in case this is new to you :-)

  • Xtext is a Language Workbench for external textual DSLs.
  • Xtext builds on Java+Eclipse.
  • Xtext is a rich grammar language.
  • Xtext builds on EMF (Eclipse Modeling Framework, a rich meta-meta model).
  • Xtext derives a EMF-based meta-model from your grammar definition (Semantic Model).
  • Xtext generates a Antlr-Parser from your grammar definition and automatically converts the AST into your semantic model.
  • Xtext generates an Eclipse-based text editor including syntax highlighting, cross-reference resolving, validation, formatting, outline-view, code-completion, rename refactoring, go to definition navigation + much more.
  • Xtext offers a rich API for validating you semantic model, but shows warnings and errors in the editor (squiggles).
  • Xtext offers a rich API for configuring your DSLs formatting (spaces, line breaks, eg).
  • Xtext lets you define custom scoping – including generic support for packages/namespaces + imports.
  • Xtext comes with a base-language (Xbase) for expressions (including compilation of expressions to Java).
  • Xtext comes with a template language (Xtend) for generating artifacts from your semantic model (model-to-model or model-to-text transformations).

Resources

Read Full Post »

Der zweite Artikel unserer DSL-Serie bei der dotnetpro ist raus. Warum der Artikel Turmbau zu Babel heißt weiß ich nicht :-)

Bild 2

Abstract: Domänenspezifische Sprachen sind eines der vielen Mittel, um Software zu industrialisieren und vor allem zu automatisieren. Praxisnah zeigt dieser Artikel eine DSL für den Import von Mainframe-Exporten – zum Beispiel, um Daten von einem Mainframe zu importieren und dabei zu konvertieren. Eclipse Xtext leistet beim Erstellen einer passenden DSL hervorragende Dienste.

Autoren: Markus Völter und Lars Corneliussen

Read Full Post »

(Find his answer here)

Dear Douglas Purdy,

Over the last several months I followed the development around “Oslo”. I couldn’t attend the PDC, but I watched all Oslo videos, read a lot of the reactions, and also spent some hands-on time writing a few schemas and grammars.

The subject I want to write to you about is particularly your talk A Lap around “Oslo” at PDC as well as your post What is Oslo?.

I’ll just quote some of your spoken and written words and put my comments below. Just for easier referencing I’ll group them in sections.

Feel free to answer on your blog in order to avoid endless comments. I suggest using the tag “Oslo2EMF”.

1. Preamble

I’ve been developing using Microsoft technologies for 7+ years now. I’m absolutely positive to Microsoft and I love .NET. Nothing here is intended to be disparaging about Microsoft.

We have realized that we live in a model-driven world.

Compared to the Java and Eclipse camps, I’d say, you realized that quite late, but still welcome and congratulations. Talking about models and particularly model-driven software development, they seem to be far ahead in both methodology and technology (more later). In a way MS also acknowledged this, by joining OMG [1]* after kind of criticizing it for a couple of years.

* see link index below for all following links

2. Terms

Now when we talk about models, and one of the things we talk about particularly with modeling inside of Microsoft, particularly with Oslo, is this notion of a very key term that I like to get across, which is Model-driven Development, Model-driven Software Development.

If you want to upset the community, just take an established term “B”, rename it to “A”, and then reinvent something slightly different branding it “B”.

My Point is that MDSD is an established term in the community. It has already been defined and there are several books available [2].

The term MDSD – as the community uses it – has its roots back in the OOPSLA in 2003 [3]. Many of the attendees did MDSD before that, but they didn’t have a term that wasn’t  trademarked (as MDA, MDD are). – Prove me wrong!

“This” MDSD stands for using models in the software creation and building process. In most cases this is done using code generation frameworks such as oAW[4] or androMDA[5].

In the Microsoft world MDSD was almost unheard [6], but Software Factories and the DSL Tools, as well as some Visual Studio built-in designers could be grouped into that category.

Now, in your talk, you renamed that approach to “model-assisted”. I think this is an appropriate term and I also understand your next point about “model-driven” in the sense of models directly executed by runtimes. But in that sense, “driven” means to drive applications and runtimes, not the development process.

To make my point: Please don’t call executing models MDSD. I think Model-driven Applications, as you also call it sometimes and as written on the slides (14:50), too, is more adequate.

3. Runtimes vs. Code Generation

To stick to your terminology, you said model-assisted development is a great way of creating software today. I do totally agree with that.

And you see some great examples on that on the slide. One of the canonical ones is HTML.

You are right. HTML is a model which is driven by several runtimes and it’s even openly specified. But it’s also an great example of how hard it is to write runtimes – All browser vendors struggle – I guess, I don’t have to say more here.

Another “great” example is the Windows Workflow Foundation. I like it. I even gave talks on it. Microsoft did well, but not good enough. They have to rewrite the runtime, the tools, …

Textual DSLs – I tell you this thing is hot!

Exactly! I totally agree, and I love MGrammar.

Q: “10x more productive with Oslo than with their current set of tools” to do what?
A: To write an .net application.

We are giving you mservice, mweb, etc.

You can, of course, write your own DSL, but we think that that will be the province of mainly ISVs and people that write frameworks today.

I totally disagree. Do you really mean what you’re saying?

DSLs are broadly used in projects, not only frameworks. You’ll miss quite a bunch of (growing) audience, if you focus only on ISVs. Big companies do big projects. And there is written millions of lines of code in such projects. MDSD, already today, boosts productivity in such projects – not 10x, but still a lot. [9]

I don’t say, runtimes are bad. But it is just not realistic to create runtimes for everything you want to base on models.

One of the reasons code-generation has so bad reputation – especially in the MS world – is because it’s often too ugly. When I talk about code generation, I mean generating well-structured, nice formatted high-quality code.

For now, Microsoft’s only answer to code generation is T4. Nice, but usually it leads to ugly code. It doesn’t even support creating multiple files for different classes.

What do you think? Do you have any plans on leveraging a code generation framework?

4. Open Microsoft and the Eclipse Modeling Project

Early, open and often. This is Pre-Pre-Pre Alpha, its raw. We want to get your feedback.

Love it!

M will be released under the Microsoft Open Specification Promise(OSP)

Does it mean, the source code will be published as a reference implementation, too? And what is M? MGrammar, MGraph, MShema, MWeb, MService, …? Will the mapping to the repository, and the definition of the repository be opened?

Anyone can implement it. We want this approach wide spread on a variety of different platforms. We want it to be as broad as Xml is today.

Quite ambitious. Where is the standards consortium, then? What about JSON?

We want to engage, particularly with the Open Source Community, in order to make sure that we can invent the future together.

I’m quite interested in what that engagement could be like, when it comes to investing effort or money.

Are we going to do anything to build a bridge to EMF, ehm, the answer is: …. ehm, we will see.

The key thing is: What I’d love to see is, I’d love to see the Eclipse Model Project, I’d love to see that community to come up with a bridge for this. So that’s part of the open – I’m not kidding.

So if they want to develop a bridge for this, absolutely I’m going to support doing that. That’s one of the key reasons we do the OSP.

I’d love to see an Eclipse “M” Project. I’d love it.

Could somebody quote me on that, so we can get that ball rolling?

Quoted. So now lets get the ball rolling. We’ve got two points here. Who is “that community”, and what kind of “bridge” do you mean?

I don’t think, Eclipse just throws their well tested products away in order to use “your” pre-pre-pre-alpha code. Asking Eclipse to implement “M”, at the first glance, sounds arrogant and kind of ridiculous to me.

But, being open, lets imagine EMF was a place and we want a nice bridge to and from Oslo.

Of course there will be the feeling of competition…
But in the end, the success of modeling is the most important thing.

Ed Merks, EMF project co-lead

Where to build the bridge? And what to archive with it?

  1. EMF (Ecore) <-> MGraph/MSchema
    If you understand both models on both sides, you’ve won alot. But mapping the schemas to Ecore could be a hard issue. EMF uses explicit inheritance, while MSchema uses structural subtyping. But still, it could be done.
  2. xtext <-> MGrammar
    Both languages transform unicode to semantic models. The concepts match, while the syntax is quite different.
  3. TBD, T4? <-> openArchitectureWare
    When it comes to code generation, M2M transformations and model checks, as said before Microsoft doesn’t offer anything even approaching openArchitectureWare.

I think the people you should get involved with are Ed Merks (EMF co-lead and Ecore-developer) as well as Sven Efftinge(xtext and oAW Project lead),  Markus Völter (xtext, oAW) and Peter Friese (xtext, xtend, oAW). [7]

Could you imagine an open space conference on this collaboration? You say “I’d love it” so often that I have to ask, how much would you love it?

Sincerely yours,

Lars Corneliussen

Links Index

Since this is a letter, I didn’t want to confuse it with links. So I indexed them and put them below.

Read Full Post »

Am Donnerstag und Freitag habe ich einen 2-tägigen Workshop zu openArchitectureWare besucht. In zwei Blogeinträgen berichte ich über das Gelernte. (Zu Teil 1)

Teil 2: Überblick über openArchitectureWare

OpenArchitectureWare ist eine Sammlung von Tools für die Modellgetriebene Software-Entwicklung. Die oAW ist Open Source, in Java implementiert und befindet sich gerade auf dem Weg in das Eclipse Modeling Framework. Schon jetzt gibt es für oAW eine gute Eclipse-Editorunterstützung

Die wesentlichen Modellierungskomponenten sind die Code-Generierung (M2T, Xpand), Modelltransformationen (M2M, Xtend), Validierung (Check) sowie die Unterstützung durch grafische und textuelle Editoren.

oAW versteht eine ganze Menge von Modellen und Modellformaten. oAW berücksichtigt die Eigenheiten der von diversen Tool-Herstellern erzeugten UML 1 und 2 XMIs in einer Masse von Parsern und formt sie zu typisierten Modellen um. Seit einiger Zeit exportieren jedoch so gut wie alle Tools konformes EMF UML 2.0, das von oAW vollständig unterstützt wird.

Sprachen in oAW

Im Rahmen der oAW wurden drei neue Sprachen erfunden, die speziell darauf abgestimmt sind, aus Modellen andere Modelle, Source Code oder andere Artefakte zu generieren. Jede Sprache beleuchtet einen definierten Aspekt, alle verfügen jedoch über ein gemeinsames Typsystem, eine einheitliche Expression-Syntax und sie lassen sich teilweise untereinander aufrufen.

Eine ausführliche Referenz zu den drei Sprachen gibt es hier.

Xtend

Xtend ist eine in Ihrer Syntax an funktionale Programmiersprachen angelegte Sprache, die zur Erweiterung der durch oAW definierten Funktionsbiliothek dient.

In einer auf “.ext” endenden Datei werden Funktionen mit Rückgaben definiert. Etwas gewöhnungsbedürftig ist, das es kein return-Schlüsselwort gibt.

Eine Methode kann auch auf dem Typ des ersten Parameters aufgerufen werden. Definiert man also eine Methode foo(String), kann diese auf einem String direkt aufgerufen werden: “hello”.foo()

Einige Beispiele:

  • // Definiert eine Methode foo() für String
    String foo(String s): s.toUpperCase();
  • // Der Parameter this ist der Kontext für direkt aufgerufene Methoden
    String foo(String this): toUpperCase();
  • // Wenn expressions mit "->" gechained werden, ist das letzte Element der
    // Chain die Rückgabe.
    // Der Aufruf erfolgt über eine Entity: myEntity.prependName("Abstract")
    Entity prependName(Entity this, String prepend): setName(prepend + name) -> this;

Aus Extend kann zusätzlich auf statische Java-Methoden zugegriffen werden. Dazu dient das Schlüsselwort JAVA.

// Die Java-Methode und ihre Signatur muss voll qualifiziert werden
String formattedToday(String format):
  JAVA myPackage.MyClass.myStaticTodayMethod(java.lang.String);

Ausführliche Referenz zu Xtend

Xpand

Xpand ist eine XSLT-ähnliche Template-Sprache, die jedoch auf die Generierung von Dateien spezialisiert ist. Neben einigen gewohnten Sprachkonstrukten wie foreach, if-then-else helfen defines und expands bei der Strukturierung der Templates.

Wie man es aus Template-Sprachen kennt, werden in die Ausgabe Codeblöcke hinein codiert. Dazu dienen die Guillements « und ».

Über das Schlüsselwort DEFINE werden Template-Methoden deklariert. Wie bei Xtend wird das Template an einen Typen (meist aus dem Metamodell) geknüpft. Die Methoden unterstützten Polimorphismus.

«DEFINE name[(parameterlist)] FOR type»
  ...code...
«ENDDEFINE»

Mittels EXPAND werden diese Templates aufgerufen:

«EXPAND name[(parameterlist)] FOR|FOREACH expression»
«REM»Kommentar: FOR ruft das Template auf dem Ergebnis der expression auf,
     FOREACH geht von einer Ergebnismenge aus und führt die Templates
     einzeln aus.«ENDREM»

Mit FILE wird der Output eines Bereiches des Templates in eine Datei umgelenkt.

«FILE "filename.java" [outlet]»
/*Dieser Inhalt wird direkt in das file "filename.java" geschrieben.
Über das outlet können zusätzlich Postprozessoren oder verschiedene Ausgabe-
Verzeichnisse angesteuert werden.*/
«EXPAND JavaClass FOREACH model().allContents.typeSelect(Entity)»
«ENDFILE»

oAW unterstützt protected regions, d.h. dass Teile des Generats beim nächsten Generieren nicht überschrieben werden und somit manuell modifiziert werden können. Diese definiert man mit PROTECT.

«PROTECT CSTART "<!--" CEND "-->" ID expression»
// TODO: Hier bitte die Methode xy ausimplementieren
throw new NotImplementedException();
«ENDPROTECT»

Wie bei den meisten Template- oder Scriptsprachen hat der Entwickler die Wahl zwischen Pest und Cholera: Soll das Generat schön aussehen, oder lieber das Template übersichtlich bleiben. Xpand bietet zwei Hebel, mit denen man dieses Problem in den Griff bekommt.

Mit einem “-” vor dem schließenden Guillement eines Script-Blocks lässt sich die Ausgabe der folgenden Whitespaces unterdrücken.

«FILE "test.txt" -»

   A ist der erste Buchstabe im Textfile. «"Und weiter? " -»
   B folgt noch in der selben Zeile.
«ENDFILE»

Zusätzlich lassen sich für bestimmte Outlets Beautifier als Postprozessoren konfigurieren, die nachträglich z.B. alle Java-Klassen vernünftig formatieren.

«FILE "TestClass.java" javaOutlet»
public
class
TestClass{
  /* Gerade bei verschachtelten Templates, die Schlüsselwörte und
     Befehle ausgeben wäre es sehr aufwendig, alle Umbrüche, Einrückungen
     und Zeilenabstände perfekt zu kontrolieren. */
}
«ENDFILE»

Dies war nur ein kleiner Auszug aus Xpand. Alle Xpand Schlüsselwörter und deren Syntax hier.

Check

Check ist eine an OCL angelehnte Sprache zur Validierung von Metamodellen oder anderen Objektbäumen. Baut man Code anhand von modellen auf, ist es wichtig, das diese formalen Regeln folgen und eindeutig sind. Diese Regeln werden mit check ausgedrückt. Besonders wichtig ist es, check bei Modell-zu-Modell-Transformationen zu verwenden, um sicherzustellen, dass das Zielmodell korrekt ist.

Einer Definition eines Fehlers oder einer Warnung für einen Typ folgen die Regeln, die nicht verletzt werden dürfen.

context type ERROR|WARNING "Message":
  rule_expression;

Beispiele:

  • context Class ERROR "Eine Klasse muss einen Namen besitzen!":
      name != null && name.lenght > 0;
  • context Entity IF isAbstract WARNING
      "Der Name abstrakter Entitäten sollte mit 'Abstract' beginnen.":
      name.startsWith("Abstract");

AOP in oAW

Die Sprachen Xtend und Xpand unterstützen Ansätze der Aspektorientierten Programmierung (AOP).

So kann man sich von außen um Templates und Extensions legen und die Kontrolle übernehmen. Das Schlüsselwort dazu ist in beiden Sprachen around.

Um die in Dateien “*.xpt” oder “*.ext” abgelegten Aspekte anzuwenden, konfiguriert man sogenannte advices.

Workflow

Als Start-Script für oAW dient ein Workflow Script das in XML beschrieben wird. Die Syntax erinnert ein wenig an Ant. In diesem Workflow werden Konfiguration, Transformationen, Generatoren, Aspekte usw. in aufeinanderfolgenden Schritten Konfiguriert.



  
    
  
  

oAW führt u.a. Komponenten für die Ausführung von Check, XPand, Xtend, Aspekte (Advices). Zur strukturierung kann man auch andere workflows aufrufen, oder mit Java eigene Komponenten erstellen.

Zwischen Komponenten werden Parameter über sog. Slot-Objekt übermittelt.

Cartridges

Cartridges sind komplette oAW-Pakete für bestimmte Anwendungsfälle. Sie bringen gewöhnlicherweise eine DSL sowie dazugehörige Metamodelle, Checks, Extensions und Generatoren.

Ein Beispiel ist die Hibenate Cartridge von Darius Jockel, mit dessen Hilfe sich Entities für die Persistenz mit Hibernate mittels UML2 modelieren lassen.

Auf der Fornax-Plattform sind viele Cartridges wie z.B. für EJB oder Spring zu finden.

Weiterführende Links

Read Full Post »

Am Donnerstag und Freitag habe ich einen 2-tägigen Workshop zu openArchitectureWare besucht. In zwei Blogeinträgen berichte ich über das Gelernte. (zu Teil 2)

Teil1: Einführung in die MDSD

Modellgetriebene Softwareentwicklung

Mit den steigenden non-funktionalen Anforderungen und zuliebe “schönen” Designs, OOP, moderner Sprachen und komplizierten Patterns wird die Fachlogik von schematischem Architekturcode ertränkt und ist kaum noch auffindbar.

Durch MDSD soll diese oft nötige Komplexität verborgen werden. Der Fokus geht zurück auf die Fachlogik; um die technischen Details kümmert sich der Generator. So lassen sich Redundanzen im Code vermeiden und es lässt sich sicherstellen, dass Architekturrichtlinien eingehalten werden. Als geliebter Nebeneffekt sinkt die Fehlerrate während die Produktivität besonders in großen Projekten schnell steigt.

Wenn ein Technologiewechsel stattfinden soll, liegen die anzupassenden Details im Generator. Die Wiederverwendung eines Models für eine vollständig andere Zielplattform ist kaum realistisch. Man kann jedoch mit relativ überschaubarem Aufwand Teilaspekte der Architektur anpassen oder austauschen.

Begriffe

Domäne: Alle Aspekte einer Applikation werden in Domänen eingeteilt uns seperat modelliert. Die richtige Aufteilung ist grundlegend für den Erfolg von MDSD.

Modell: Ein abstrahiertes Abbild eines Teilaspektes der Realität. Bei Modellen ist wichtig nur einen sehr deutlich abgegrenzten Aspekt zu betrachten. Aus mehreren eindeutigen Modellen entsteht das Gesamtbild.

Metamodell: Beschreibt die Struktur für ein Modell. Im Vergleich zu Objektorientierung ist das Metamodell die Klasse, wärend das Modell die Instanz ist. Ein Metamodell ist in vielen Fällen die Implementierung einer DSL, d.h. die Verwendung fachlicher Begrifflichkeiten der Domäne sind erwünscht.

Metametamodell: Ein selbstbeschreibendes meist recht einfaches Modell zur Beschreibung der Grammatik von Metamodellen. Hier wird keine Rücksicht auf fachliche Domänen genommen, sondern der Bezug ist rein technisch. Beispielsweise MOF oder EMF. Diese nimmt man normalerweise als gegeben, und fängt nicht an sie selbst zu bauen.

Die funktionsweise von MDSD

Aus dem formalen Modell wird gewöhnlicherweise ein auf die technische Problemstellung abgestimmtes Zwischenmodell generiert. Daraus erstellt der Generator Code um diesen mit manuell geschriebener Fachlogik zusammenzuführen. Das Ergebnis ist lauffähiger Code.

MDSD vs. MDA

Die MDA ist ein OMG-Standard, der sehr teoretisch ist. In MDA wird optimalerweise vollkommen platformabhängig definiert, während in MDSD gerne etwas mehr Bezug auf die Platttform, den Prozess und die Quellcodegenerierung genommen wird.

Domänenspezifische Sprachen (DSL)

DSLs werden auf eine bestimmte Problem-Domäne zugeschnitten und sind somit ein Metamodell. Eine DSL muss eindeutig sein, sprich eine formale Syntax und eine formale Semantik besitzen.

Gute DSLs sind einfach und nicht redundant. Sie reflektieren die Sprache des Domänenexperten und sind von solchen zumindest lesbar. Dies macht Teile der Dokumentation überflüssig.

Tools wie Xtext, EMF/Ecore oder Language Workbench helfen bei der Erstellung von DSLs. Bei der Konzeption muss darauf geachtet werden, dass sie für Änderungen offen bleibt.

DSLs und UML

Oft werden DSLs mit UML2 beschrieben. Dies hat den wesentlichen Vorteil, dass UML ein Standard ist und viele grafische Tools beim erstellen der Modelle unterstützen. Leider ist UML praktisch nicht einschränkbar, sondern nur durch Profile und Beschränkungen erweiterbar. Da diese von Tools nicht durchgehend unterstützt werden, ist es jedoch so gut wie unmöglich eindeutige DSLs zu definieren.

Das einzige Format, das zum Austausch von UML-Modellen taugt ist EMF UML2. Der Export in dieses Format wird jedoch noch nicht von allen Toolherstellern unterstützt.

DSLs mit EMF/Ecore

Ein DSL kann it EMF modelliert werden. Eine grafische Unterstützung kann man über GMF erstellen.

DSLs mit xtext

Xtext ist eine DSL zur Beschreibung der Grammatik von textuellen DSLs. Xtext ist Teil des oAW-Projektes und bietet dem Entwickler Möglichkeiten zur formalen Validierung seiner Modelle sowie in Eclipse integrierte Editoren mit Intellisense, etc.

Read Full Post »

Follow

Get every new post delivered to your Inbox.

Join 433 other followers