/*******************************************************************************
* Copyright (c) 2013, 2013 Bruno Medeiros and other Contributors.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Bruno Medeiros - initial API and implementation
*******************************************************************************/
package dtool.dub;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertNotNull;
import static melnorme.utilbox.misc.MiscUtil.createPath;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import melnorme.utilbox.misc.ArrayUtil;
import melnorme.utilbox.misc.MiscUtil;
import melnorme.utilbox.misc.PathUtil.InvalidPathExceptionX;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.MalformedJsonException;
import dtool.dub.DubBundle.BundleFile;
import dtool.dub.DubBundle.DubBundleException;
import dtool.dub.DubBundle.DubDependecyRef;
import dtool.util.JsonReaderExt;
/**
* Parse a Dub bundle in a filesystem location into an in-memory description of the bundle.
*/
public class DubManifestParser extends CommonDubParser {
public static final String ERROR_BUNDLE_NAME_UNDEFINED = "Bundle name not defined.";
public static DubBundle parseDubBundleFromLocation(BundlePath bundlePath) {
assertNotNull(bundlePath);
return new DubManifestParser().parseDubBundle(bundlePath);
}
protected String source;
protected String bundleName = null;
protected String pathString = null;
protected String version = null;
protected String[] sourceFolders = null;
protected List<BundleFile> bundleFiles = null;
protected DubDependecyRef[] dependencies = null;
protected String targetName = null;
protected String targetPath = null;
protected DubManifestParser() {
}
protected DubBundle parseDubBundle(BundlePath bundlePath) {
assertNotNull(bundlePath);
try {
parseFromLocation(bundlePath);
} catch (DubBundleException e) {
dubError = e;
}
return createBundle(bundlePath, true);
}
protected void parseFromLocation(BundlePath bundlePath) throws DubBundleException {
File jsonLocation = bundlePath.getManifestFilePath().toFile();
try {
source = readStringFromFile(jsonLocation);
} catch (IOException e) {
throw new DubBundleException(e);
}
parseFromSource(source);
}
public DubBundle createBundle(BundlePath bundlePath, boolean searchImplicitSourceFolders) {
if(bundleName == null) {
bundleName = "<undefined>";
putError(ERROR_BUNDLE_NAME_UNDEFINED);
}
if(bundlePath == null) {
if(pathString == null) {
putError("Missing path entry.");
} else {
bundlePath = BundlePath.create(pathString);
if(bundlePath == null) {
putError("Invalid path: " + pathString);
}
}
}
Path[] effectiveSourceFolders;
if(sourceFolders != null) {
effectiveSourceFolders = createPaths(sourceFolders);
} else if(searchImplicitSourceFolders && bundlePath != null) {
effectiveSourceFolders = searchImplicitSrcFolders(bundlePath.path);
} else {
effectiveSourceFolders = null;
}
return new DubBundle(bundlePath, bundleName, dubError, version,
sourceFolders, effectiveSourceFolders,
bundleFiles,
dependencies, targetName, targetPath);
}
protected Path[] createPaths(String[] paths) {
if(paths == null)
return null;
ArrayList<Path> pathArray = new ArrayList<>();
for (String pathString : paths) {
try {
pathArray.add(createPath(pathString));
} catch (InvalidPathExceptionX e) {
putError("Invalid source/import path: " + pathString);
}
}
return ArrayUtil.createFrom(pathArray, Path.class);
}
protected Path[] searchImplicitSrcFolders(Path location) {
if(location == null) {
return new Path[0];
}
File locationDir = location.toFile();
if(!locationDir.isDirectory()) {
putError("location is not a directory");
return new Path[0];
}
final ArrayList<Path> implicitFolders = new ArrayList<>();
locationDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
if(dir.isDirectory() && (name.equals("src") || name.equals("source"))) {
implicitFolders.add(MiscUtil.createValidPath(name));
}
return false;
}
});
return ArrayUtil.createFrom(implicitFolders, Path.class);
}
@Override
protected void readData(JsonReaderExt jsonParser) throws IOException {
readBundle(jsonParser);
}
protected DubManifestParser readBundle(JsonReaderExt jsonReader) throws IOException {
jsonReader.consumeExpected(JsonToken.BEGIN_OBJECT);
while(jsonReader.hasNext()) {
JsonToken tokenType = jsonReader.peek();
if(tokenType == JsonToken.NAME) {
String propertyName = jsonReader.nextName();
if(propertyName.equals("name")) {
bundleName = jsonReader.consumeStringValue();
} else if(propertyName.equals("version")) {
version = jsonReader.consumeStringValue();
} else if(propertyName.equals("path")) {
pathString = jsonReader.consumeStringValue();
} else if(propertyName.equals("importPaths")) {
sourceFolders = parseSourcePaths(jsonReader);
} else if(propertyName.equals("dependencies")) {
dependencies = parseDependencies(jsonReader);
} else if(propertyName.equals("files")) {
bundleFiles = parseFiles(jsonReader);
} else if(propertyName.equals("targetName")) {
targetName = jsonReader.consumeStringValue();
} else if(propertyName.equals("targetPath")) {
targetPath = jsonReader.consumeStringValue();
} else {
jsonReader.skipValue();
}
} else {
jsonReader.errorUnexpected(tokenType);
}
}
jsonReader.consumeExpected(JsonToken.END_OBJECT);
return this;
}
protected String[] parseSourcePaths(JsonReaderExt jsonReader) throws IOException {
ArrayList<String> stringArray = jsonReader.consumeStringArray(true);
return ArrayUtil.createFrom(stringArray, String.class);
}
protected ArrayList<BundleFile> parseFiles(JsonReaderExt jsonReader) throws IOException {
jsonReader.consumeExpected(JsonToken.BEGIN_ARRAY);
ArrayList<BundleFile> bundleFiles = new ArrayList<>();
while(jsonReader.hasNext()) {
BundleFile bundleFile = parseFile(jsonReader);
bundleFiles.add(bundleFile);
}
jsonReader.consumeExpected(JsonToken.END_ARRAY);
return bundleFiles;
}
protected BundleFile parseFile(JsonReaderExt jsonReader) throws IOException {
jsonReader.consumeExpected(JsonToken.BEGIN_OBJECT);
String path = null;
boolean importOnly = false;
while(jsonReader.hasNext()) {
String propName = jsonReader.consumeExpectedPropName();
switch(propName) {
case "path":
path = jsonReader.consumeStringValue();
break;
case "type":
//TODO
default:
jsonReader.skipValue();
}
}
jsonReader.consumeExpected(JsonToken.END_OBJECT);
if(path == null) {
path = "<missing_path>";
putError("missing path property for files entry.");
}
return new DubBundle.BundleFile(path, importOnly);
}
protected DubDependecyRef[] parseDependencies(JsonReaderExt jsonParser) throws IOException {
return new BundleDependenciesSegmentParser(jsonParser).parse();
}
protected class BundleDependenciesSegmentParser {
protected JsonReaderExt jsonReader;
public BundleDependenciesSegmentParser(JsonReaderExt jsonParser) {
this.jsonReader = jsonParser;
}
public DubDependecyRef[] parse() throws IOException {
if(jsonReader.peek() == JsonToken.BEGIN_OBJECT)
return parseRawDeps();
return parseResolvedDeps();
}
public DubDependecyRef[] parseRawDeps() throws IOException, MalformedJsonException {
jsonReader.consumeExpected(JsonToken.BEGIN_OBJECT);
ArrayList<DubDependecyRef> deps = new ArrayList<>();
while(jsonReader.hasNext()) {
String depName = jsonReader.consumeExpectedPropName();
jsonReader.skipValue(); // Ignore value for now, TODO
deps.add(new DubDependecyRef(depName, null));
}
jsonReader.consumeExpected(JsonToken.END_OBJECT);
return ArrayUtil.createFrom(deps, DubDependecyRef.class);
}
public DubDependecyRef[] parseResolvedDeps() throws IOException, MalformedJsonException {
jsonReader.consumeExpected(JsonToken.BEGIN_ARRAY);
ArrayList<DubDependecyRef> deps = new ArrayList<>();
while(jsonReader.hasNext()) {
String depName = jsonReader.nextString();
deps.add(new DubDependecyRef(depName, null));
}
jsonReader.consumeExpected(JsonToken.END_ARRAY);
return ArrayUtil.createFrom(deps, DubDependecyRef.class);
}
}
}