-
Notifications
You must be signed in to change notification settings - Fork 105
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
implement prototype of FxmlAnalyzer #425
- Loading branch information
1 parent
0f24f59
commit 5221151
Showing
48 changed files
with
837 additions
and
0 deletions.
There are no files selected for viewing
155 changes: 155 additions & 0 deletions
155
mvvmfx-dev-tools/src/main/java/de/saxsys/mvvmfx/devtools/core/fxmlanalyzer/FxmlAnalyzer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
package de.saxsys.mvvmfx.devtools.core.fxmlanalyzer; | ||
|
||
|
||
import de.saxsys.mvvmfx.FxmlView; | ||
import de.saxsys.mvvmfx.devtools.core.fxmlanalyzer.util.Either; | ||
import de.saxsys.mvvmfx.devtools.core.fxmlanalyzer.warnings.ControllerClassNotFoundWarning; | ||
import de.saxsys.mvvmfx.devtools.core.fxmlanalyzer.warnings.InvalidXmlWarning; | ||
import de.saxsys.mvvmfx.devtools.core.fxmlanalyzer.warnings.SrcPathNotFoundWarning; | ||
import de.saxsys.mvvmfx.devtools.core.fxmlanalyzer.warnings.Warning; | ||
import de.saxsys.mvvmfx.internal.viewloader.FxmlViewLoaderUtils; | ||
import org.w3c.dom.Document; | ||
import org.w3c.dom.Element; | ||
import org.w3c.dom.Node; | ||
import org.w3c.dom.NodeList; | ||
import org.xml.sax.SAXException; | ||
|
||
import javax.xml.parsers.DocumentBuilder; | ||
import javax.xml.parsers.DocumentBuilderFactory; | ||
import javax.xml.parsers.ParserConfigurationException; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.net.URL; | ||
import java.nio.file.Path; | ||
import java.nio.file.Paths; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Objects; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.IntStream; | ||
import java.util.stream.Stream; | ||
|
||
public class FxmlAnalyzer { | ||
|
||
|
||
public static ViewNode loadViewTree(Class<? extends FxmlView> rootViewType) { | ||
Objects.requireNonNull(rootViewType); | ||
final String fxmlPath = FxmlViewLoaderUtils.resolveFxmlPath(rootViewType); | ||
|
||
return loadFromPath(fxmlPath); | ||
} | ||
|
||
private static DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); | ||
|
||
|
||
|
||
private static ViewNode loadFromPath(String relativePath) { | ||
final String relativeFxmlPath = relativePath.replace('\\', '/'); | ||
|
||
ViewNode viewNode = new ViewNode(relativeFxmlPath); | ||
|
||
URL fxmlUrl = FxmlAnalyzer.class.getResource(relativeFxmlPath); | ||
|
||
if(fxmlUrl == null) { | ||
viewNode.addWarnings(new SrcPathNotFoundWarning()); | ||
return viewNode; | ||
} | ||
|
||
|
||
Either<Document, Warning> documentEither = loadDocument(fxmlUrl); | ||
|
||
if(documentEither.isRight()) { | ||
viewNode.addWarnings(documentEither.getRightUnsafe()); | ||
} else { | ||
Document document = documentEither.getLeftUnsafe(); | ||
|
||
|
||
// search for child views | ||
List<ViewNode> children = loadSubViewNodes(relativeFxmlPath, document); | ||
viewNode.addChildren(children); | ||
|
||
|
||
// search for a controller class | ||
String controllerFQN = document.getDocumentElement().getAttribute("fx:controller"); | ||
|
||
if(controllerFQN != null) { | ||
Either<Class, Warning> controllerClass = getControllerClassFromFQN(controllerFQN); | ||
|
||
if(controllerClass.isLeft()) { | ||
|
||
Class controller = controllerClass.getLeftUnsafe(); | ||
viewNode.setControllerType(controller); | ||
} else { | ||
// a controller class was specified but couldn't be found | ||
viewNode.addWarnings(controllerClass.getRightUnsafe()); | ||
} | ||
} | ||
|
||
|
||
// search for ViewModel class | ||
// todo | ||
} | ||
|
||
return viewNode; | ||
} | ||
|
||
private static List<ViewNode> loadSubViewNodes(String relativeFxmlPath, Document document) { | ||
// source paths of fx:include elements | ||
List<String> includes = resolveFxIncludesFromDoc(document); | ||
|
||
Path relativeRootPath = Paths.get(relativeFxmlPath); | ||
|
||
return includes.stream() | ||
.map(relativeRootPath::resolveSibling) | ||
// recursive method call | ||
.map(subViewPath -> loadFromPath(subViewPath.toString())) | ||
.filter(Objects::nonNull) | ||
.collect(Collectors.toList()); | ||
} | ||
|
||
private static Either<Class, Warning> getControllerClassFromFQN(String fullyQualifiedName) { | ||
try { | ||
return Either.left(FxmlAnalyzer.class.getClassLoader().loadClass(fullyQualifiedName)); | ||
} catch (ClassNotFoundException e) { | ||
return Either.right(new ControllerClassNotFoundWarning(fullyQualifiedName)); | ||
} | ||
} | ||
|
||
private static List<String> resolveFxIncludesFromDoc(Document document) { | ||
NodeList fxIncludes = document.getElementsByTagName("fx:include"); | ||
if(fxIncludes.getLength() == 0) { | ||
return Collections.emptyList(); | ||
} | ||
|
||
Stream<Node> nodeStream = IntStream.range(0, fxIncludes.getLength()) | ||
.mapToObj(fxIncludes::item); | ||
|
||
return nodeStream.filter(node -> node.getNodeType() == Node.ELEMENT_NODE) | ||
.map(node -> (Element) node) | ||
.map(element -> element.getAttribute("source")) | ||
.filter(Objects::nonNull) | ||
.filter(source -> !source.trim().isEmpty()) | ||
.collect(Collectors.toList()); | ||
} | ||
|
||
private static Either<Document, Warning> loadDocument(URL path) { | ||
try { | ||
DocumentBuilder documentBuilder = dbFactory.newDocumentBuilder(); | ||
|
||
InputStream inputStream = path.openStream(); | ||
|
||
Document parsedDocument = documentBuilder.parse(inputStream); | ||
parsedDocument.getDocumentElement().normalize(); | ||
|
||
return Either.left(parsedDocument); | ||
} catch (ParserConfigurationException e) { | ||
// this case shouldn't happen. | ||
// It would be a mean a bug in the program itself and not the document of the user. | ||
throw new IllegalStateException(e); | ||
} catch (IOException e) { | ||
return Either.right(new SrcPathNotFoundWarning()); | ||
} catch (SAXException e) { | ||
return Either.right(new InvalidXmlWarning()); | ||
} | ||
} | ||
} |
100 changes: 100 additions & 0 deletions
100
mvvmfx-dev-tools/src/main/java/de/saxsys/mvvmfx/devtools/core/fxmlanalyzer/ViewNode.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package de.saxsys.mvvmfx.devtools.core.fxmlanalyzer; | ||
|
||
import de.saxsys.mvvmfx.FxmlView; | ||
import de.saxsys.mvvmfx.ViewModel; | ||
import de.saxsys.mvvmfx.devtools.core.fxmlanalyzer.warnings.Warning; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.Collection; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Optional; | ||
|
||
/** | ||
* This class represents a node in a FXML tree. | ||
*/ | ||
public class ViewNode { | ||
|
||
private String fxmlPath; | ||
private Class<?> viewType; | ||
private Class<? extends ViewModel> viewModelType; | ||
private List<ViewNode> children = new ArrayList<>(); | ||
private List<Warning> warnings = new ArrayList<>(); | ||
|
||
public ViewNode(String fxmlPath) { | ||
this.fxmlPath = fxmlPath; | ||
} | ||
|
||
void addWarnings(Warning... warnings) { | ||
switch (warnings.length) { | ||
case 0: | ||
break; | ||
case 1: | ||
this.warnings.add(warnings[0]); | ||
break; | ||
default: | ||
this.warnings.addAll(Arrays.asList(warnings)); | ||
break; | ||
} | ||
} | ||
|
||
void setControllerType(Class<?> controllerType) { | ||
this.viewType = controllerType; | ||
} | ||
|
||
void addChildren(Collection<ViewNode> children) { | ||
this.children.addAll(children); | ||
} | ||
|
||
void addViewModelType(Class<? extends ViewModel> viewModelType) { | ||
this.viewModelType = viewModelType; | ||
} | ||
|
||
public String getFxmlPath() { | ||
return fxmlPath; | ||
} | ||
|
||
|
||
/** | ||
* The Optional returned by this method will contain the | ||
* class type of the controller class defined in the fxml root element with "fx:controller". | ||
* <br /> | ||
* If the fxml document doesn't define a controller with the "fx:controller" attribute | ||
* the Optional will be empty. | ||
* <br /> | ||
* | ||
* The difference between this method and {@link #getViewType()} is that | ||
* the Optional returned by this method will contain the class type of the controller even | ||
* if it isn't a mvvmFX view class. | ||
*/ | ||
public Optional<Class<?>> getControllerClass() { | ||
return Optional.ofNullable(viewType); | ||
} | ||
|
||
/** | ||
* If this {@link ViewNode} represents a mvvmFX fxml view | ||
* the returned Optional will contain the Class type of the View/CodeBehind | ||
* that implements {@link FxmlView}. | ||
* <br /> | ||
* | ||
* Otherwise the returned Optional will be empty. | ||
*/ | ||
public Optional<Class<? extends FxmlView>> getViewType() { | ||
return getControllerClass() | ||
.filter(FxmlView.class::isAssignableFrom) | ||
.map(type -> (Class<? extends FxmlView>) type); | ||
} | ||
|
||
public Optional<Class<? extends ViewModel>> getViewModelType() { | ||
return Optional.ofNullable(viewModelType); | ||
} | ||
|
||
public List<ViewNode> getChildren() { | ||
return Collections.unmodifiableList(children); | ||
} | ||
|
||
public List<Warning> getWarnings() { | ||
return Collections.unmodifiableList(warnings); | ||
} | ||
} |
62 changes: 62 additions & 0 deletions
62
mvvmfx-dev-tools/src/main/java/de/saxsys/mvvmfx/devtools/core/fxmlanalyzer/util/Either.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package de.saxsys.mvvmfx.devtools.core.fxmlanalyzer.util; | ||
|
||
import java.util.Optional; | ||
import java.util.function.Function; | ||
|
||
|
||
/** | ||
* Either class represents a result that can be one of two possible result types. | ||
* <p/> | ||
* A typical usage case for the Either type is as a result type for methods that may be successful or fail | ||
* and when no exceptions are to be used. | ||
* In this scenario the {@link #left} type represents the result of the method when the operation | ||
* was successful. The {@link #right} type represents the result of the method when the operation | ||
* has failed. In this case {@link #right} typically contains a failure message or some similar type that | ||
* represents the reason for failure. | ||
* | ||
* @param <L> | ||
* @param <R> | ||
*/ | ||
public class Either<L, R> { | ||
|
||
private final L left; | ||
private final R right; | ||
|
||
private Either(L left, R right) { | ||
this.left = left; | ||
this.right = right; | ||
} | ||
|
||
public static <L, R> Either<L,R> left(L left) { | ||
return new Either<>(left, null); | ||
} | ||
|
||
public static <L, R> Either<L,R> right(R right) { | ||
return new Either<>(null, right); | ||
} | ||
|
||
public L getLeftUnsafe() { | ||
return left; | ||
} | ||
|
||
public R getRightUnsafe() { | ||
return right; | ||
} | ||
|
||
public Optional<L> getLeft() { | ||
return Optional.ofNullable(left); | ||
} | ||
|
||
public Optional<R> getRight() { | ||
return Optional.ofNullable(right); | ||
} | ||
|
||
public boolean isLeft() { | ||
return left != null; | ||
} | ||
|
||
public boolean isRight() { | ||
return right != null; | ||
} | ||
|
||
} |
13 changes: 13 additions & 0 deletions
13
.../de/saxsys/mvvmfx/devtools/core/fxmlanalyzer/warnings/ControllerClassNotFoundWarning.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package de.saxsys.mvvmfx.devtools.core.fxmlanalyzer.warnings; | ||
|
||
public class ControllerClassNotFoundWarning implements Warning { | ||
private final String classFQN; | ||
|
||
public ControllerClassNotFoundWarning(String classFQN) { | ||
this.classFQN = classFQN; | ||
} | ||
|
||
public String getClassFQN() { | ||
return classFQN; | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
...ls/src/main/java/de/saxsys/mvvmfx/devtools/core/fxmlanalyzer/warnings/GenericWarning.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package de.saxsys.mvvmfx.devtools.core.fxmlanalyzer.warnings; | ||
|
||
|
||
public class GenericWarning implements Warning { | ||
|
||
private final String message; | ||
|
||
public GenericWarning(String message) { | ||
this.message = message; | ||
} | ||
|
||
public String getMessage() { | ||
return message; | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
...saxsys/mvvmfx/devtools/core/fxmlanalyzer/warnings/IncludeElementWithoutSourceWarning.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package de.saxsys.mvvmfx.devtools.core.fxmlanalyzer.warnings; | ||
|
||
public class IncludeElementWithoutSourceWarning implements Warning { | ||
|
||
} |
6 changes: 6 additions & 0 deletions
6
...src/main/java/de/saxsys/mvvmfx/devtools/core/fxmlanalyzer/warnings/InvalidXmlWarning.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package de.saxsys.mvvmfx.devtools.core.fxmlanalyzer.warnings; | ||
|
||
public class InvalidXmlWarning implements Warning{ | ||
|
||
|
||
} |
6 changes: 6 additions & 0 deletions
6
...ain/java/de/saxsys/mvvmfx/devtools/core/fxmlanalyzer/warnings/SrcPathNotFoundWarning.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package de.saxsys.mvvmfx.devtools.core.fxmlanalyzer.warnings; | ||
|
||
public class SrcPathNotFoundWarning implements Warning { | ||
|
||
|
||
} |
4 changes: 4 additions & 0 deletions
4
...dev-tools/src/main/java/de/saxsys/mvvmfx/devtools/core/fxmlanalyzer/warnings/Warning.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package de.saxsys.mvvmfx.devtools.core.fxmlanalyzer.warnings; | ||
|
||
public interface Warning { | ||
} |
Oops, something went wrong.