Skip to content

Commit

Permalink
implement prototype of FxmlAnalyzer #425
Browse files Browse the repository at this point in the history
  • Loading branch information
manuel-mauky committed Aug 8, 2016
1 parent 0f24f59 commit 5221151
Show file tree
Hide file tree
Showing 48 changed files with 837 additions and 0 deletions.
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());
}
}
}
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);
}
}
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;
}

}
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;
}
}
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;
}
}
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 {

}
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{


}
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 {


}
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 {
}
Loading

0 comments on commit 5221151

Please sign in to comment.