2012-10-16 4 views
9

Es ist ein Konstruktor mit drei Parametern vom Typ Enum:Compiletime Validierung von Enum Parameter

public SomeClass(EnumType1 enum1,EnumType2 enum2, EnumType3 enum3) 
{...} 

Die drei Parameter vom Typ Enum sind nicht mit allen möglichen Werten kombiniert werden allowd:

Beispiel:

EnumType1.VALUE_ONE, EnumType2.VALUE_SIX, EnumType3.VALUE_TWENTY ist eine gültige Kombination.

aber die folgende Kombination ist nicht gültig:

EnumType1.VALUE_TWO, EnumType2.VALUE_SIX, EnumType3.VALUE_FIFTEEN

Jede der EnumTypes mit dem weiß-Werte es sein darf kombiniert:

EnumType1 und die beiden anderen eine isAllowedWith() Methode implementieren, um zu überprüfen, dass wie folgt:

public enum EnumType1 { 

VALUE_ONE,VALUE_TWO,...; 

    public boolean isAllowedWith(final EnumType2 type) { 
    switch (this) { 
     case VALUE_ONE: 
      return type.equals(Type.VALUE_THREE); 
     case VALUE_TWO: 
      return true; 
     case VALUE_THREE: 
      return type.equals(Type.VALUE_EIGHT); 
     ... 
    } 
} 

Ich muss diese Prüfung zur Kompilierzeit ausführen, da es in meinem Projekt von äußerster Wichtigkeit ist, dass die Kombinationen zur Laufzeit IMMER korrekt sind.

Ich frage mich, ob es eine Möglichkeit gibt, diese Prüfung mit benutzerdefinierten Anmerkungen auszuführen?

ist Jede Idee geschätzt :)

+1

Dies ist auf jeden Fall, dass etwas, das Sie können und mit tun sollten [apt] (http://docs.oracle.com/javase/1.5.0/docs/guide/apt/GettingStarted.html), obwohl ich nicht bin vertraut genug, um eine qualifizierte Antwort zu schreiben. –

Antwort

3

So ist der einfachste Weg, dies in der Dokumentation zu 1) Definieren Sie zu tun ist, gültige Kombinationen und
2) fügen Sie die Kontrollen im Konstruktor

Wenn ein Konstruktor zu erklären löst eine Ausnahme aus, für die der Aufrufer verantwortlich ist. Grundsätzlich würden Sie so etwas tun:

public MyClass(enum foo, enum bar, enum baz) 
{ 
    if(!validateCombination(foo,bar,baz)) 
    { 
     throw new IllegalStateException("Contract violated"); 
    } 
} 


private boolean validateCombination(enum foo, enum bar, enum baz) 
{ 
    //validation logic 
} 

Jetzt ist dieser Teil absolut kritisch. Markieren Sie die Klasse als final. Es ist möglich, dass ein teilweise erstelltes Objekt wiederhergestellt und missbraucht werden kann, um Ihre Anwendung zu unterbrechen. Mit einer Klasse, die als endgültig markiert ist, kann ein bösartiges Programm das teilweise konstruierte Objekt nicht erweitern und Chaos anrichten.

+1

Aber das ist immer noch keine Kompilierzeitprüfung – giorashc

+1

Was ist mit IllegalArgumentException anstelle von IllegalStateException? –

+1

@BheshGurung technisch ist das Programm in einem illegalen Zustand, mehr als es ein illegales Argument ist. – Woot4Moo

0

Nun, ich kenne keine Kompilierzeitprüfung, aber ich glaube nicht, dass das möglich ist, weil der Compiler weiß, welcher Wert an den Konstruktor übergeben wird (falls der Wert Ihrer enum-Variablen in Runtime berechnet wird) (zB durch eine If-Klausel)? Dies kann nur zur Laufzeit validiert werden, indem eine Validator-Methode verwendet wird, wie Sie sie für die Enum-Typen implementiert haben.

Beispiel:

Wenn in Ihrem Code Sie so etwas wie dieses:

EnumType1 enumVal; 

if (<some condition>) { 
    enumVal = EnumType2.VALUE_SIX; 
} else { 
    enumVal = EnumType2.VALUE_ONE; 
} 

Es gibt keine Möglichkeit der Compiler, welche der Werte wissen können, werden enumVal zugewiesen werden, so wird es nicht sein, die Lage zu überprüfen, was an den Konstruktor übergeben wird, bis der if-Block (der nur im laufenden Betrieb durchgeführt werden kann) ausgewertet wird

4

die Pfosten oben keine Lösung für Kompilierung-Check bringen, hier ist meins:

Warum nicht das Konzept von verschachteltEnum verwenden.

Sie hätten EnumType1 mit eigenen Werten + eine verschachtelte EnumType2 und diese eine verschachtelte EnumType3.

Sie könnten das Ganze mit Ihrer nützlichen Kombination organisieren. Sie könnten mit 3 Klassen (EnumType1,2 und 3) und jedem einzelnen betroffenen Wert, der die anderen enthält, die zulässigen Werte erhalten.

Und Ihr Anruf würde so aussehen (mit vorausgesetzt, Sie EnumType1.VALUE_ONE mit EnumType2.VALUE_FIFTEEN zugeordnet werden soll):

EnumType1.VALUE_ONE.VALUE_FIFTEEN //second value corresponding to EnumType2 

So könnten Sie haben auch: EnumType3.VALUE_SIX.VALUE_ONE (wo SIX von Typ3 und ONE von Typ1 bekannt ist).

würde Ihr Anruf ändern, wie etwas sein:

public SomeClass(EnumType1 enumType) 

=> Beispiel:

SomeClass(EnumType1.VALUE_ONE.VALUE_SIX.VALUE_TWENTY) //being a valid combination as said 

Um besser zu klären es, versuchen Sie es zu diesem Beitrag: Using nested enum types in Java

+0

Aber Sie können immer noch EnumType1.VALUE_TWO.VALUE_SIX.VALUE_FIFTEEN tun, was im OP-Fall illegal ist – giorashc

+5

Wie gut skaliert das? – Woot4Moo

+0

Nein, du kannst nicht. VALUE_FIFTEEN wäre in seinen verschachtelten Enums nicht enthalten. Ich gebe nur das Konzept, Werte, die ich gewählt habe, sind für das Beispiel. – Mik378

2

Eine alternative Idee ist, Schreiben Sie einige automatisierte Tests, um dies zu erfassen, und haken Sie sie in Ihren Build-Prozess als einen obligatorischen Schritt vor Packa Ging/Bereitstellung Ihrer App.

Wenn Sie darüber nachdenken, was Sie versuchen, hier zu fangen, ist es Code, der legal aber falsch ist. Während Sie dies während der Kompilierungsphase feststellen konnten, sind Tests genau dafür gedacht.

Dies entspricht Ihrer Anforderung, keinen Code mit einer ungültigen Kombination erstellen zu können, da der Build weiterhin fehlschlägt. Und es wäre wohl für andere Entwickler leichter zu verstehen, als einen eigenen Annotationsprozessor zu schreiben ...

+0

Sie haben Recht, richtige Tests zu postulieren ... das machen wir schon. Es ist einfach zu unelegant, diese Art von logisch falschem Code auf diese Weise zu entdecken. – SixDoubleFiveTreeTwoOne

+0

Ich denke, Sie können für beide Seiten Argumente vorbringen, da Entwickler in Ihrer Situation gültiges Java schreiben würden, was im Kontext Ihrer Anwendung keinen Sinn ergibt. Das ist im Großen und Ganzen, was Tests fangen sollen. (Obwohl ich auch einen guten Compiler-Check genieße, kann ich Ihnen nicht die Schuld daran geben, nach Alternativen zu suchen.) –

1

Der einzige Weg, den ich kenne, ist mit Annotationen zu arbeiten.

Hier ist was ich meine ich meine. nun Ihren Konstruktor akzeptiert drei Parameter:

SomeClass obj = new SomeClass(EnumTupe1.VALUE1, EnumTupe2.VALUE2, EnumTupe1.VALUE3)

Ändern Sie den Konstruktor als privat:

public SomeClass(EnumType1 enum1,EnumType2 enum2, EnumType3 enum3){}

so werden Sie es wie folgt aufrufen. Erstellen Sie einen öffentlichen Konstruktor, der 1 Parameter eines beliebigen Typs akzeptiert. Es kann sich nur um einen falschen Parameter handeln.

public SomeClass(Placeholder p)

Jetzt müssen Sie benötigen diesen Konstruktor aufrufen, während jedes Argument mit speziellen Anmerkung kommentiert wird. Nennen wir es TypeAnnotation:

SomeClass obj = new SomeClass(TypeAnnotation(
    type1=EnumType1.VALUE1, 
    type2=EnumTupe2.VALUE2, 
    type3=EnumTupe1.VALUE3) 
    p3); 

Der Aufruf ist ausführlicher, aber das ist, was wir für Kompilierung Validierung bezahlen.

Nun, wie die Anmerkung definieren?

@Documented @Retention ({RetentionPolicy.RUNTIME, RetentionPolicy.SOURCE}) @Target (Parameter) @interface TypeAnnotation { EnumType1 Typ1(); EnumType2 type3(); EnumType3 type3(); }

Bitte beachten Sie, dass das Ziel PARAMETER ist und die Aufbewahrungswerte RUNTIME und SOURCE sind.

RUNTIME ermöglicht das Lesen dieser Annotation zur Laufzeit, während SOURCE das Erstellen eines Annotation-Prozessors ermöglicht, der die Parameter zur Laufzeit validieren kann.

Nun ist die öffentliche Konstruktor wird die 3-Parameter Privat construcor nennen:

öffentliche Someclass (Platzhalter p) { diese (readAnnotation (EnumType1.class), readAnnotation (EnumType2.class), readAnnotation (EnumType3. Klasse),) }

ich Umsetzung bin nicht readAnnotation() hier: es sollte statische Methode sein, die Stack-Trace nimmt, geht 3 Elemente zurück (zum Aufrufer des öffentlichen costructor) und parst Anmerkung TypeAnnotation.

Jetzt ist der interessanteste Teil. Sie müssen den Annotationsprozessor implementieren. Werfen Sie einen Blick here Anweisungen und here für ein Beispiel von Annotation-Prozessor.

Sie haben die Nutzung dieser Annotation-Prozessor zu Ihrem Build-Skript und (optional), um Ihre IDE hinzuzufügen. In diesem Fall erhalten Sie einen echten Kompilierungsfehler, wenn Ihre Kompatibilitätsregeln verletzt werden.

Ich glaube, dass diese Lösung zu kompliziert aussieht, aber wenn Sie das wirklich benötigen, können Sie dies tun. Es kann einen Tag oder so dauern. Viel Glück.

+0

Danke. Ich werde einen Blick darauf werfen. – SixDoubleFiveTreeTwoOne