/**
* Copyright 2005-2013 Dozer Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dozer.eclipse.plugin.sourcepage.validation;
import org.apache.xerces.dom.NodeImpl;
import org.apache.xerces.parsers.DOMParser;
import org.apache.xerces.xni.Augmentations;
import org.apache.xerces.xni.QName;
import org.apache.xerces.xni.XMLAttributes;
import org.apache.xerces.xni.XMLLocator;
import org.apache.xerces.xni.XNIException;
import org.dozer.eclipse.plugin.sourcepage.util.DozerPluginUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.wst.xml.core.internal.validation.core.AbstractNestedValidator;
import org.eclipse.wst.xml.core.internal.validation.core.NestedValidatorContext;
import org.eclipse.wst.xml.core.internal.validation.core.ValidationInfo;
import org.eclipse.wst.xml.core.internal.validation.core.ValidationReport;
import org.springframework.ide.eclipse.core.java.JdtUtils;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.UserDataHandler;
import org.xml.sax.InputSource;
import java.io.InputStream;
import java.net.URI;
@SuppressWarnings("restriction")
public class Validator extends AbstractNestedValidator {
@Override
public ValidationReport validate(String uri, InputStream inputstream,
NestedValidatorContext context) {
ValidationInfo validationReport = new ValidationInfo(uri);
try {
//Get IFile from URI
IFile[] files = ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(new URI(uri.replace(" ", "%20")));
//hmm, could be multiple? Lets just take the first
IFile file = files[0];
// FIXME this is sick.
// To get the line/column-numbers for the broken custom-converter properties, we need to
// extend the DOMParser class to get the location information.
// No idea whether this will work everywhere. I bet there is a a better solution somewhere...
LocationTrackingDOMParser parser = new LocationTrackingDOMParser();
parser.setFeature("http://apache.org/xml/features/dom/defer-node-expansion", false); //must disable that feature to get location
parser.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
parser.parse(new InputSource(uri));
Document doc = parser.getDocument(); //Parse the Doc (every node gets UserData-Info for location info, see below)
//DocumentType docType = doc.getDoctype();
//String sysId = null;
//if (docType != null)
// sysId = docType.getSystemId();
//if (sysId != null && "http://dozer.sourceforge.net/dtd/dozerbeanmapping.dtd".equals(sysId)) {
if ("mapping".equals(doc.getDocumentElement().getNodeName()))
//classes exist?
checkClassNodes(doc.getElementsByTagName("class-a"), file, validationReport);
checkClassNodes(doc.getElementsByTagName("class-b"), file, validationReport);
//field correct?
checkFieldNodes(doc.getElementsByTagName("field"), file, validationReport);
checkFieldNodes(doc.getElementsByTagName("field-exclude"), file, validationReport);
//}
} catch (Throwable e) {
e.printStackTrace();
}
return validationReport;
}
@SuppressWarnings("restriction")
private void checkClassNodes(NodeList nodeList, IFile file, ValidationInfo validationReport) throws JavaModelException, DOMException {
int len = nodeList.getLength();
for (int i = 0; i < len; i++) {
Node node = nodeList.item(i);
Node textNode = node.getFirstChild();
if (textNode.getNodeType() == Node.TEXT_NODE) {
String className = textNode.getNodeValue();
IType javaType = JdtUtils.getJavaType(file.getProject(), className);
//class not found
if (javaType == null) {
Integer[] location = (Integer[])node.getUserData("location");
validationReport.addError("Class "+className+" not found.", location[0], location[1], validationReport.getFileURI());
}
}
}
}
@SuppressWarnings("restriction")
private void checkFieldNodes(NodeList nodeList, IFile file, ValidationInfo validationReport) throws JavaModelException, DOMException {
int len = nodeList.getLength();
//check all field-nodes...
for (int i = 0; i < len; i++) {
Node node = nodeList.item(i); //field
//..have custom-converter attributes
Node attrNode = node.getAttributes().getNamedItem("custom-converter");
if (attrNode != null) {
//...doesnt implement the CustomConverter Interface
if (!checkClassImplementsCustomConverter(file.getProject(), attrNode.getNodeValue())) {
//this class doesnt implement the interface and is worth an error
Integer[] location = (Integer[])node.getUserData("location");
validationReport.addError("Class does not implement interface CustomConverter", location[0], location[1], validationReport.getFileURI());
}
//...have custom-converter-id attributes
} else {
attrNode = node.getAttributes().getNamedItem("custom-converter-id");
if (attrNode != null) {
String className = DozerPluginUtils.getClassNameForCCI(file, attrNode.getNodeValue());
if (className != null && !checkClassImplementsCustomConverter(file.getProject(), className)) {
//this class doesnt implement the interface and is worth an error
Integer[] location = (Integer[])node.getUserData("location");
validationReport.addError("Bean does not implement interface CustomConverter", location[0], location[1], validationReport.getFileURI());
}
}
}
NodeList abList = node.getChildNodes();
int abLen = abList.getLength();
for (int a = 0; a < abLen; a++) {
Node abNode = abList.item(a); //a or b
if ("a".equals(abNode.getNodeName()) || "b".equals(abNode.getNodeName())) {
Node textNode = abNode.getFirstChild();
//...no value set between <a></a> or <b></b>?
if(textNode == null){
Integer[] location = (Integer[])node.getUserData("location");
String className = DozerPluginUtils.getMappingClassName(abNode);
String nodeName = abNode.getNodeName();
validationReport.addError("Unsetted property value in node "+nodeName+" for class " + className, location[0], location[1], validationReport.getFileURI());
}
//...is fieldname correct?
else if (textNode.getNodeType() == Node.TEXT_NODE) {
String property = textNode.getNodeValue();
String className = DozerPluginUtils.getMappingClassName(abNode);
attrNode = DozerPluginUtils.getMappingNode(abNode).getAttributes().getNamedItem("type");
boolean bIsBiDirectional = true;
if (attrNode != null) {
bIsBiDirectional = "bi-directional".equals(attrNode.getNodeValue());
}
if (!"this".equals(property)) {
Node isAccessibleNode = abNode.getAttributes().getNamedItem("is-accessible");
boolean isAccessible = isAccessibleNode != null && "true".equals(isAccessibleNode.getNodeValue());
if ((bIsBiDirectional || "a".equals(abNode.getNodeName())) &&
DozerPluginUtils.hasReadProperty(property, className, file.getProject(), isAccessible) == null) {
Integer[] location = (Integer[])abNode.getUserData("location");
validationReport.addError("Property "+property+" for class "+className+" cannot be read from.", location[0], location[1], validationReport.getFileURI());
} else if ((bIsBiDirectional || "b".equals(abNode.getNodeName())) &&
DozerPluginUtils.hasWriteProperty(property, className, file.getProject()) == null) {
Integer[] location = (Integer[])abNode.getUserData("location");
validationReport.addError("Property "+property+" for class "+className+" cannot be written to.", location[0], location[1], validationReport.getFileURI());
}
}
}
}
}
}
}
private boolean checkClassImplementsCustomConverter(IProject project, String className) throws JavaModelException {
IType javaType = JdtUtils.getJavaType(project, className);
if (javaType == null)
return false;
ITypeHierarchy hierarchy = javaType.newSupertypeHierarchy(new NullProgressMonitor());
IType[] interfaces = hierarchy.getAllSuperInterfaces(javaType);
for (IType type : interfaces) {
if (type.getElementName().equals("CustomConverter")) {
return true;
}
}
return false;
}
/**
* Borrowed and enhanced from Cocoon Woody DOMHelper Class
*/
public static class LocationTrackingDOMParser extends DOMParser {
XMLLocator locator;
@Override
public void startDocument(XMLLocator xmlLocator,
String arg1, org.apache.xerces.xni.NamespaceContext arg2,
Augmentations arg3) throws org.apache.xerces.xni.XNIException {
super.startDocument(xmlLocator, arg1, arg2, arg3);
this.locator = xmlLocator;
setLocation();
}
@Override
public void startElement(QName qName, XMLAttributes xmlAttributes,
Augmentations augmentations) throws XNIException {
super.startElement(qName, xmlAttributes, augmentations);
setLocation();
}
private final void setLocation() {
if (this.locator == null) {
throw new RuntimeException(
"Error: locator is null. Check that you have the" +
" correct version of Xerces (such as the one that" +
" comes with Cocoon) in your endorsed library path.");
}
NodeImpl node = null;
try {
node = (NodeImpl)this.getProperty(
"http://apache.org/xml/properties/dom/current-element-node");
} catch (org.xml.sax.SAXException ex) {
System.err.println("except" + ex);
}
if (node != null) {
Integer[] location = new Integer[] {locator.getLineNumber(), locator.getColumnNumber()};
node.setUserData("location", location, (UserDataHandler)null);
}
}
}
}