Java 8: Formatieren von Lambda mit Zeilenumbrüchen und Einrückungen


82

Was ich mit Lambda-Einrückung erreichen möchte, ist Folgendes:

Mehrzeilige Anweisung:

String[] ppl = new String[] { "Karen (F)", "Kevin (M)", "Lee (M)", "Joan (F)", "Des (M)", "Rick (M)" };
List<String> strings = Arrays.stream(ppl)
                         .filter(
                             (x) -> 
                             {
                                 return x.contains("(M)");
                             }
                         ).collect(Collectors.toList());
strings.stream().forEach(System.out::println);

Einzeilige Anweisung:

List<String> strings = Arrays.stream(ppl)
                         .map((x) -> x.toUpperCase())
                         .filter((x) -> x.contains("(M)"))
                         .collect(Collectors.toList());



Derzeit formatiert Eclipse automatisch wie folgt:

Mehrzeilige Anweisung:

String[] ppl = new String[] { "Karen (F)", "Kevin (M)", "Lee (M)", "Joan (F)", "Des (M)", "Rick (M)" };
List<String> strings = Arrays.stream(ppl).filter((x) ->
{
    return x.contains("(M)");
}).collect(Collectors.toList());
strings.stream().forEach(System.out::println);

Einzeilige Anweisung:

String[] ppl = new String[] { "Karen (F)", "Kevin (M)", "Lee (M)", "Joan (F)", "Des(M)", "Rick (M)" };
List<String> strings = Arrays.stream(ppl).map((x) -> x.toUpperCase())
        .filter((x) -> x.contains("(M)")).collect(Collectors.toList());
strings.stream().forEach(System.out::println);

Und ich finde das wirklich chaotisch, weil der collectAnruf direkt unter dem ist returnund dazwischen überhaupt kein Platz ist. Ich würde es vorziehen, wenn ich das Lambda in einer neuen eingerückten Zeile starten könnte und der .filter(Anruf direkt über dem .collect(Anruf wäre. Das einzige, was mit dem Standard-Java-8-Eclipse-Formatierer angepasst werden kann, ist die Klammer am Anfang des Lambda-Körpers, aber nichts für die ()Klammern im Voraus oder die Einrückung.

Und bei einzeiligen Anrufen wird nur der grundlegende Zeilenumbruch verwendet und es wird zu einem verketteten Durcheinander. Ich glaube nicht, dass ich erklären muss, warum dies später schwer zu entschlüsseln ist.

Gibt es eine Möglichkeit, die Formatierung irgendwie weiter anzupassen und den ersten Formatierungstyp in Eclipse zu erreichen? (Oder optional in einer anderen IDE wie IntelliJ IDEA.)



BEARBEITEN: Das nächste, was ich bekommen konnte, war mit IntelliJ IDEA 13 Community Edition (lesen: kostenlose Ausgabe: P), das das Folgende war (definiert durch fortlaufende Einrückung, in diesem Fall 8):

public static void main(String[] args)
{
    int[] x = new int[] {1, 2, 3, 4, 5, 6, 7};
    int sum = Arrays.stream(x)
            .map((n) -> n * 5)
            .filter((n) -> {
                System.out.println("Filtering: " + n);
                return n % 3 != 0;
            })
            .reduce(0, Integer::sum);

    List<Integer> list = Arrays.stream(x)
            .filter((n) -> n % 2 == 0)
            .map((n) -> n * 4)
            .boxed()
            .collect(Collectors.toList());
    list.forEach(System.out::println);
    System.out.println(sum);    

Es ermöglicht auch das "Ausrichten" des verketteten Methodenaufrufs wie folgt:

    int sum = Arrays.stream(x)
                    .map((n) -> n * 5)
                    .filter((n) -> {
                        System.out.println("Filtering: " + n);
                        return n % 3 != 0;
                    })
                    .reduce(0, Integer::sum);


    List<Integer> list = Arrays.stream(x)
                               .filter((n) -> n % 2 == 0)
                               .map((n) -> n * 4)
                               .boxed()
                               .collect(Collectors.toList());
    list.forEach(System.out::println);
    System.out.println(sum);
}

Ich persönlich finde, dass es zwar sinnvoller ist, die zweite Version es jedoch viel zu weit wegschiebt, daher bevorzuge ich die erste.

Das Setup, das für das erste Setup verantwortlich ist, ist das folgende:

<?xml version="1.0" encoding="UTF-8"?>
<code_scheme name="Zhuinden">
  <option name="JD_ALIGN_PARAM_COMMENTS" value="false" />
  <option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
  <option name="JD_ADD_BLANK_AFTER_PARM_COMMENTS" value="true" />
  <option name="JD_ADD_BLANK_AFTER_RETURN" value="true" />
  <option name="JD_P_AT_EMPTY_LINES" value="false" />
  <option name="JD_PARAM_DESCRIPTION_ON_NEW_LINE" value="true" />
  <option name="WRAP_COMMENTS" value="true" />
  <codeStyleSettings language="JAVA">
    <option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
    <option name="BRACE_STYLE" value="2" />
    <option name="CLASS_BRACE_STYLE" value="2" />
    <option name="METHOD_BRACE_STYLE" value="2" />
    <option name="ELSE_ON_NEW_LINE" value="true" />
    <option name="WHILE_ON_NEW_LINE" value="true" />
    <option name="CATCH_ON_NEW_LINE" value="true" />
    <option name="FINALLY_ON_NEW_LINE" value="true" />
    <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
    <option name="SPACE_WITHIN_BRACES" value="true" />
    <option name="SPACE_BEFORE_IF_PARENTHESES" value="false" />
    <option name="SPACE_BEFORE_WHILE_PARENTHESES" value="false" />
    <option name="SPACE_BEFORE_FOR_PARENTHESES" value="false" />
    <option name="SPACE_BEFORE_TRY_PARENTHESES" value="false" />
    <option name="SPACE_BEFORE_CATCH_PARENTHESES" value="false" />
    <option name="SPACE_BEFORE_SWITCH_PARENTHESES" value="false" />
    <option name="SPACE_BEFORE_SYNCHRONIZED_PARENTHESES" value="false" />
    <option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
    <option name="METHOD_PARAMETERS_WRAP" value="1" />
    <option name="EXTENDS_LIST_WRAP" value="1" />
    <option name="THROWS_LIST_WRAP" value="1" />
    <option name="EXTENDS_KEYWORD_WRAP" value="1" />
    <option name="THROWS_KEYWORD_WRAP" value="1" />
    <option name="METHOD_CALL_CHAIN_WRAP" value="2" />
    <option name="BINARY_OPERATION_WRAP" value="1" />
    <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
    <option name="ASSIGNMENT_WRAP" value="1" />
    <option name="IF_BRACE_FORCE" value="3" />
    <option name="DOWHILE_BRACE_FORCE" value="3" />
    <option name="WHILE_BRACE_FORCE" value="3" />
    <option name="FOR_BRACE_FORCE" value="3" />
    <option name="PARAMETER_ANNOTATION_WRAP" value="1" />
    <option name="VARIABLE_ANNOTATION_WRAP" value="1" />
    <option name="ENUM_CONSTANTS_WRAP" value="2" />
  </codeStyleSettings>
</code_scheme>

Ich habe versucht, sicherzustellen, dass alles in Ordnung ist, aber ich habe möglicherweise etwas durcheinander gebracht, sodass möglicherweise geringfügige Anpassungen erforderlich sind.

Wenn Sie wie ich ungarisch sind und ein ungarisches Layout verwenden, kann diese Keymap für Sie von Nutzen sein, damit Sie AltGR + F, AltGR + G, AltGR + B nicht verwenden können , AltGR + N und AltGR + M (entsprechend Strg + Alt).

<?xml version="1.0" encoding="UTF-8"?>
<keymap version="1" name="Default copy" parent="$default">
  <action id="ExtractMethod">
    <keyboard-shortcut first-keystroke="shift control M" />
  </action>
  <action id="GotoImplementation">
    <mouse-shortcut keystroke="control alt button1" />
  </action>
  <action id="GotoLine">
    <keyboard-shortcut first-keystroke="shift control G" />
  </action>
  <action id="Inline">
    <keyboard-shortcut first-keystroke="shift control O" />
  </action>
  <action id="IntroduceField">
    <keyboard-shortcut first-keystroke="shift control D" />
  </action>
  <action id="Mvc.RunTarget">
    <keyboard-shortcut first-keystroke="shift control P" />
  </action>
  <action id="StructuralSearchPlugin.StructuralReplaceAction" />
  <action id="Synchronize">
    <keyboard-shortcut first-keystroke="shift control Y" />
  </action>
</keymap>

Während IntelliJ keine Möglichkeit zu bieten scheint, die öffnende Klammer des Lambda in eine neue Zeile zu setzen, ist dies ansonsten eine ziemlich vernünftige Art der Formatierung, daher werde ich dies als akzeptiert markieren.


10
Kannst du nicht benutzen .filter(x -> x.contains("(M)"))? Viel einfacher ... Wenn Sie tatsächlich über Orte sprechen, an denen Sie mehrere Anweisungen benötigen, ist es besser, ein Beispiel anzugeben, das diese benötigt.
Jon Skeet

@ JonSkeet Ich konnte mir momentan keinen wirklich guten Anwendungsfall für Mehrzeilen vorstellen, aber ich habe stattdessen beide Fälle hinzugefügt.
EpicPandaForce

2
Ehrlich gesagt, war die 'Netbeans 8.0'-Version, die jemand unten gepostet und gelöscht hat, der Wahrheit sehr nahe, obwohl Netbeans dafür berüchtigt ist, die ersten Klammern von Anweisungen im Allman-Stil nicht richtig zu formatieren.
EpicPandaForce

3
Ich denke, die aktuelle Lösung besteht darin, "niemals umschlossene Zeilen verbinden" und die Formatierung von Hand zu ermöglichen, aber das scheint etwas mühsam im Vergleich zum Drücken, Ctrl+Alt+Fwenn Sie den Code eines anderen automatisch formatieren.
EpicPandaForce

Und aus irgendeinem Grund hatte Eclipse Probleme, den Typ herauszufinden, xohne die geschweifte Klammer zu öffnen und zu schließen, weshalb ich zuerst eine Aussage anstelle der einfachen Version verwendete.
EpicPandaForce

Antworten:


29

Sofort einsatzbereit IntelliJ 13 wird wahrscheinlich für Sie funktionieren.

Wenn ich es so schreibe:

// Mulit-Line Statement
String[] ppl = new String[] { "Karen (F)", "Kevin (M)", "Lee (M)", "Joan (F)", "Des (M)", "Rick (M)" };
List<String> strings = Arrays.stream(ppl)
        .filter(
                (x) ->
                {
                    return x.contains("(M)");
                }
        ).collect(Collectors.toList());
strings.stream().forEach(System.out::println);

Wenden Sie dann den automatischen Formatierer an (keine Änderungen):

// Mulit-Line Statement
String[] ppl = new String[]{"Karen (F)", "Kevin (M)", "Lee (M)", "Joan (F)", "Des (M)", "Rick (M)"};
List<String> strings = Arrays.stream(ppl)
        .filter(
                (x) ->
                {
                    return x.contains("(M)");
                }
        ).collect(Collectors.toList());
strings.stream().forEach(System.out::println);

Gleiches gilt für Ihre einzeilige Anweisung. Ich habe die Erfahrung gemacht, dass IntelliJ flexibler bei der Anwendung der automatischen Formatierung ist. Es ist weniger wahrscheinlich, dass IntelliJ Zeilenrückgaben entfernt oder hinzufügt. Wenn Sie es dort ablegen, wird davon ausgegangen, dass Sie beabsichtigt haben, es dort abzulegen. IntelliJ passt Ihren Tab-Bereich gerne für Sie an.


IntelliJ kann auch so konfiguriert werden, dass dies für Sie erledigt wird. Unter "Einstellungen" -> "Codestil" -> "Java" können Sie auf der Registerkarte "Umbrechen und Klammern" "Kettenmethodenaufrufe" auf "Immer umbrechen" setzen.

Vor dem automatischen Formatieren

// Mulit-Line Statement
List<String> strings = Arrays.stream(ppl).filter((x) -> { return x.contains("(M)"); }).collect(Collectors.toList());

// Single-Line Statement
List<String> strings = Arrays.stream(ppl).map((x) -> x.toUpperCase()).filter((x) -> x.contains("(M)")).collect(Collectors.toList());

Nach der automatischen Formatierung

// Mulit-Line Statement
List<String> strings = Arrays.stream(ppl)
        .filter((x) -> {
            return x.contains("(M)");
        })
        .collect(Collectors.toList());

// Single-Line Statement
List<String> strings = Arrays.stream(ppl)
        .map((x) -> x.toUpperCase())
        .filter((x) -> x.contains("(M)"))
        .collect(Collectors.toList());

Die Frage ist, ob es in der Lage ist, es automatisch so zu formatieren. Es vereinfacht das Problem, wenn die automatische Formatierung einen guten Job beim Formatieren des Codes macht, so wie ich den Code gestalten möchte. Auf diese Weise ist jeder Code einfacher zu lesen, selbst der Code anderer.
EpicPandaForce

@Zhuinden Ich habe die Antwort mit einem Beispiel aktualisiert, wie dies für eine einzeilige Anweisung gemacht wird. Wie gezeigt, funktioniert es bis zu einem gewissen Grad auch für die mehrzeilige Anweisung. Es gibt viele verschiedene Optionen zum Konfigurieren des automatischen Formatierers von IntelliJ. Sie können ihn wahrscheinlich so verfeinern, dass er genau so angezeigt wird, wie Sie ihn in Ihrer Frage haben.
Mike Rylander

Ich habe die Dinge gemäß dem oben eingefügten XML eingerichtet. Ich denke, ich werde IntelliJ anstelle von Eclipse für alles verwenden, was nicht Android ist. Vielen Dank.
EpicPandaForce

1
@EpicPandaForce warum sollten Sie es nicht auch für Android verwenden?
Hermann

@herman Technisch gesehen benutze ich seitdem auch Android Studio für Android. Ich musste ein paar zusätzliche GB RAM installieren, aber die Firma hatte einige
Probleme

59

In Eclipse für die einzeiligen Anweisungen:

Gehen Sie in Ihrem Projekt oder in Ihren globalen Java -> Code Style -> Formatter -> Edit -> Line Wrapping -> Function Calls -> Qualified InvocationsEinstellungen zu , legen Sie fest Wrap all elements, except first if not necessaryund aktivieren Sie diese Option Force split, even if line shorter than maximum line width.


Hey, das scheint in Ordnung zu sein, außer ich bin gezwungen einzurücken, auch das entweder mit Standard-Indendation oder Spalte oder 1. Das macht keinen Sinn. Meine Einrückung unterscheidet sich je nach Strom. Beispiel: endgültige Liste <DataRow> Tage = MappedSelect.query ("gameDays", DataRow.class) .param ("SeasonId", 20142015) .select (Kontext); Für das obige möchte ich, dass die nächsten 2 Zeilen genau auf dem vorherigen Zeilenpunkt eingerückt werden.
Saravana Kumar M

1
Fast perfekt! :-) Manchmal wird es jedoch zu viel für meine Präferenz .filter(c -> c.getValue().equals(newValue))umbrochen : Aussagen innerhalb des Lambda, z. B. werden auch kurz vor den .equals umbrochen. Wäre schön, wenn eine solche Einstellung nur für Lambda-bezogene Methoden gelten würde; Ich sehe, dass dies schwierig zu implementieren sein könnte ...
Dominic

24

Eclipse (Mars) bietet eine Option für den Formatierer von Lambda-Ausdrücken.

Gehe zu Window > Preferences > Java > Code Style > Formatter

Geben Sie hier die Bildbeschreibung ein

Klicken Sie auf die EditSchaltfläche, gehen Sie zum BracesTag und setzen Sie das Lambda BodyaufNext Line Indented

Geben Sie hier die Bildbeschreibung ein

Eine weitere Option ist das Aktualisieren dieser Eigenschaften in Ihren Projekteinstellungen. ( yourWorkspace > yourProject > .settings > org.eclipse.jdt.core.prefs)

org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert
org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=next_line_shifted

2
Dies scheint nur bei Lambdas in Klammern zu helfen.
Nick

Das Aktualisieren dieser Eigenschaften hilft nicht
Piotr Wittchen

19

Diese Frage ist jetzt alt und leider ist die Standardkonfiguration des Eclipse-Formatierers immer noch nicht benutzerfreundlich, um Funktionscode lesbar zu schreiben.

Ich habe alle in allen anderen Antworten genannten Dinge ausprobiert und für die meisten Anwendungsfälle passt niemand.
Es mag für einige in Ordnung sein, für andere jedoch unangenehm.

Ich habe einen Weg gefunden, der mir die meiste Zeit passt.
Ich teile es, indem ich denke, dass es anderen helfen könnte.

Beachten Sie, dass mein Weg einen Kompromiss hat: Akzeptieren, dass jeder qualifizierte Aufruf immer in einer bestimmten Zeile steht.
Dies ist möglicherweise die fehlende Option in der Formatierungskonfiguration: Angabe des Schwellenwerts in Bezug auf Aufrufe zum Umbrechen der Zeile, anstatt 1standardmäßig den Aufruf zu verwenden.

Hier sind meine 2 kombinierten Werkzeuge, um damit richtig umzugehen:

  • Anpassen der Eclipse-Formatierungskonfiguration für die meisten Fälle

  • Erstellen einer Codevorlage mit //@formatter:off ... //@formatter:onfür Eckfälle.


Anpassen der Konfiguration
des Eclipse-Formatierers Der zu ändernde Wert ist in der Aufnahme rot umrandet.

Schritt 1) ​​Erstellen Sie Ihren eigenen Java Code Style Formatter

PreferencesMenü und im Baum gehen Sie zu Java -> Code Style -> Formatter.
Klicken Sie auf "Neu", um ein neues zu erstellen Profile(initialisieren Sie es mit "Java-Konventionen").

Eclipse-Formatierer

Die beiden nächsten Schritte müssen in Ihrem benutzerdefinierten Formatierungsprofil ausgeführt werden.

Schritt 2) Ändern Sie die Einrückungskonfiguration für umbrochene Zeilen

Identifikationskonfiguration

Die Änderung ermöglicht die Verwendung von Leerzeichen anstelle von Tabellen.
Dies ist im nächsten Schritt von Bedeutung, da wir die Zeilenumbruchrichtlinie mit der Option zum Einrücken von Spalten konfigurieren.
Es wird in der Tat vermieden, unangenehme Räume zu schaffen.

Schritt 3) Ändern Sie den Standardeinzug für umbrochene Zeilen und die Zeilenumbruchrichtlinie für qualifizierten Aufruf

Zeilenumbruchgröße


Hier ist eine Testformatierung mit dem Code der Frage.

Vor dem Formatieren:

void multiLineStatements() {
    String[] ppl = new String[] { "Karen (F)", "Kevin (M)", "Lee (M)", "Joan (F)", "Des (M)", "Rick (M)" };
    List<String> strings = Arrays.stream(ppl).filter((x) ->
    {
        return x.contains("(M)");
    }).collect(Collectors.toList());
    strings.stream().forEach(System.out::println);
}

void singleLineStatements() {
    String[] ppl = new String[] { "Karen (F)", "Kevin (M)", "Lee (M)", "Joan (F)", "Des(M)", "Rick (M)" };
    List<String> strings = Arrays.stream(ppl).map((x) -> x.toUpperCase())
            .filter((x) -> x.contains("(M)")).collect(Collectors.toList());
    strings.stream().forEach(System.out::println);
}

Nach der Formatierung:

void multiLineStatements() {
    String[] ppl = new String[] { "Karen (F)", "Kevin (M)", "Lee (M)", "Joan (F)", "Des (M)", "Rick (M)" };
    List<String> strings = Arrays.stream(ppl)
                                 .filter((x) -> {
                                     return x.contains("(M)");
                                 })
                                 .collect(Collectors.toList());
    strings.stream()
           .forEach(System.out::println);
}

void singleLineStatements() {
    String[] ppl = new String[] { "Karen (F)", "Kevin (M)", "Lee (M)", "Joan (F)", "Des(M)", "Rick (M)" };
    List<String> strings = Arrays.stream(ppl)
                                 .map((x) -> x.toUpperCase())
                                 .filter((x) -> x.contains("(M)"))
                                 .collect(Collectors.toList());
    strings.stream()
           .forEach(System.out::println);
}

Erstellen von Codevorlagen mit //@formatter:off ... //@formatter:onfür Eckfälle.

Das Schreiben von Hand oder Copy-Paste //@formatter:onund //@formatter:offist in Ordnung , wie Sie es selten schreiben.
Wenn Sie es jedoch mehrmals pro Woche oder noch schlimmer bei Tag schreiben müssen, ist ein automatischerer Weg willkommen.

Schritt 1) ​​Wechseln Sie zur Java Editor-Vorlage

PreferencesMenü und im Baum gehen Sie zu Java ->Editor -> Template.
Geben Sie hier die Bildbeschreibung ein

Schritt 2) Erstellen Sie eine Vorlage, um die Formatierung für den ausgewählten Code zu deaktivieren

Vorlagenformatierer aus an

Sie können es jetzt testen.
Wählen Sie Zeilen aus, für die Sie die Formatierung deaktivieren möchten.
Geben Sie nun ctrl+spacezweimal ein (der erste ist "Java-Vorschläge" und der zweite ist "Vorlagenvorschläge").
Sie sollten etwas bekommen wie:

Vorlagenvorschlag

Wählen Sie die fmtVorlage wie im Screenshot aus und klicken Sie auf "Enter". Erledigt!

Ergebnis nach Vorlagenanwendung


16

Ich formatiere einzeilige Anweisungen, indem ich nach Funktionen einen leeren Kommentar "//" hinzufüge.

List<Integer> list = Arrays.stream(x) //
                           .filter((n) -> n % 2 == 0) //
                           .map((n) -> n * 4) //
                           .boxed() //
                           .collect(Collectors.toList());

3
Einfach und effektiv.
Steve

12
Ich denke, es ist Rauschen in Ihrer Codebasis, nur weil Sie keine IDE-Stilkonfiguration festgelegt haben. Es kann verwirrend sein, wenn Leute zum ersten Mal auf Ihre Codebasis schauen.
Marc Bouvier

8

Nicht ideal, aber Sie können den Formatierer nur für die Abschnitte ausschalten, die etwas dicht sind. Zum Beispiel

  //@formatter:off
  int sum = Arrays.stream(x)
        .map((n) -> n * 5)
        .filter((n) -> {
            System.out.println("Filtering: " + n);
            return n % 3 != 0;
        })
        .reduce(0, Integer::sum);
  //@formatter:on

Gehen Sie zu "Fenster> Einstellungen> Java> Codestil> Formatierer". Klicken Sie auf die Schaltfläche "Bearbeiten ...", wechseln Sie zur Registerkarte "Aus / Ein-Tags" und aktivieren Sie die Tags.


3
Dies schien eine ungekünstige, aber einfachere Option zu sein, um meine Formatierung korrekt zu machen
smc

2

Die Option, die für mich funktioniert hat, war das Aktivieren der Never join already wrapped linesOption im Abschnitt Zeilenumbruch des Formatierers in den Einstellungen.


1

Wenn Sie noch keinen benutzerdefinierten Eclipse-Formatierer haben:

Eclipse-Einstellungen Java> Codestil> Formatierer Neu Geben Sie einen Namen ein. Klicken Sie auf OK. Zeilenumbrüche steuern

Bearbeiten des Profils Registerkarte "Zeilenumbruch" Aktivieren Sie "Niemals bereits umbrochene Zeilen verbinden".

Es wird so enden

    String phrase = employeeList
            .stream()
            .filter(p -> p.getAge() >= 33)
            .map(p -> p.getFirstName())
            .collect(Collectors.joining(" and ", "In Germany ", " are of legal age."));

Müssen alle eingeben. Beginnen Sie in der nächsten Zeile

Credits - https://www.selikoff.net/2017/07/02/eclipse-and-line-wrapping/


-1

Die neueste Version von Eclipse verfügt über einen integrierten Formatierer für die Java 8-Formatierung

Preferences -> Java -> Code Style -> Formatter -> Active profile

and Select Eclipse 2.1[built-in] profile
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.