/*******************************************************************************
* Copyright (c) 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.StringUtil.nullAsEmpty;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
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.DubConfiguration;
import dtool.util.JsonReaderExt;
import melnorme.lang.tooling.BundlePath;
import melnorme.lang.tooling.bundle.DependencyRef;
import melnorme.utilbox.collections.ArrayList2;
import melnorme.utilbox.core.CommonException;
import melnorme.utilbox.misc.ArrayUtil;
import melnorme.utilbox.misc.FileUtil;
import melnorme.utilbox.misc.Location;
import melnorme.utilbox.misc.MiscUtil;
import melnorme.utilbox.misc.PathUtil;
import melnorme.utilbox.misc.StringUtil;
/**
* 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 parseDubBundleFromLocation2(BundlePath bundlePath) {
assertNotNull(bundlePath);
Location manifestLocation = bundlePath.getManifestLocation(true);
if(manifestLocation != null && nullAsEmpty(manifestLocation.getFileName()).endsWith(".sdl")) {
return null; // We only know how to parse JSON
}
return new DubManifestParser().parseDubBundle(bundlePath, manifestLocation);
}
protected String source;
protected String bundleName = null;
protected String pathString = null;
protected String version = null;
protected String[] sourceFolders = null;
protected ArrayList2<BundleFile> bundleFiles = null;
protected DependencyRef[] dependencies = null;
protected String targetName = null;
protected String targetPath = null;
protected ArrayList2<DubConfiguration> configurations = null;
protected DubManifestParser() {
}
protected DubBundle parseDubBundle(BundlePath bundlePath, Location manifestLocation) {
try {
parseFromLocation(manifestLocation);
} catch (DubBundleException e) {
dubError = e;
}
return createBundle(bundlePath, true);
}
protected void parseFromLocation(Location jsonManifestLocation) throws DubBundleException {
try {
source = FileUtil.readStringFromFile(jsonManifestLocation.toFile(), StringUtil.UTF8);
} 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.getLocation());
} else {
effectiveSourceFolders = null;
}
return new DubBundle(
bundlePath, bundleName, dubError, version,
sourceFolders, effectiveSourceFolders,
bundleFiles,
dependencies, targetName, targetPath, configurations
);
}
protected Path[] createPaths(String[] paths) {
if(paths == null)
return null;
ArrayList<Path> pathArray = new ArrayList<>();
for (String pathString : paths) {
try {
pathArray.add(PathUtil.createPath(pathString, "Invalid source/import path: "));
} catch (CommonException ce) {
putError(ce.getMessage());
}
}
return ArrayUtil.createFrom(pathArray, Path.class);
}
protected Path[] searchImplicitSrcFolders(Location 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, DubBundleException {
readBundle(jsonParser);
}
protected DubManifestParser readBundle(JsonReaderExt jsonReader) throws IOException, DubBundleException {
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 if(propertyName.equals("configurations")) {
configurations = parseConfigurations(jsonReader);
} 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 ArrayList2<BundleFile> parseFiles(JsonReaderExt jsonReader) throws IOException {
jsonReader.consumeExpected(JsonToken.BEGIN_ARRAY);
ArrayList2<BundleFile> bundleFiles = new ArrayList2<>();
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 DependencyRef[] parseDependencies(JsonReaderExt jsonParser) throws IOException {
return new BundleDependenciesSegmentParser(jsonParser).parse();
}
protected class BundleDependenciesSegmentParser {
protected JsonReaderExt jsonReader;
public BundleDependenciesSegmentParser(JsonReaderExt jsonParser) {
this.jsonReader = jsonParser;
}
public DependencyRef[] parse() throws IOException {
if(jsonReader.peek() == JsonToken.BEGIN_OBJECT)
return parseRawDeps();
return parseResolvedDeps();
}
public DependencyRef[] parseRawDeps() throws IOException, MalformedJsonException {
jsonReader.consumeExpected(JsonToken.BEGIN_OBJECT);
ArrayList<DependencyRef> deps = new ArrayList<>();
while(jsonReader.hasNext()) {
String depName = jsonReader.consumeExpectedPropName();
jsonReader.skipValue(); // Ignore value for now, TODO
deps.add(new DependencyRef(depName, null));
}
jsonReader.consumeExpected(JsonToken.END_OBJECT);
return ArrayUtil.createFrom(deps, DependencyRef.class);
}
public DependencyRef[] parseResolvedDeps() throws IOException, MalformedJsonException {
jsonReader.consumeExpected(JsonToken.BEGIN_ARRAY);
ArrayList<DependencyRef> deps = new ArrayList<>();
while(jsonReader.hasNext()) {
String depName = jsonReader.nextString();
deps.add(new DependencyRef(depName, null));
}
jsonReader.consumeExpected(JsonToken.END_ARRAY);
return ArrayUtil.createFrom(deps, DependencyRef.class);
}
}
protected ArrayList2<DubConfiguration> parseConfigurations(JsonReaderExt jsonReader)
throws IOException, DubBundleException {
jsonReader.consumeExpected(JsonToken.BEGIN_ARRAY);
ArrayList2<DubConfiguration> bundleFiles = new ArrayList2<>();
while(jsonReader.hasNext()) {
DubConfiguration element = parseConfiguration(jsonReader);
bundleFiles.add(element);
}
jsonReader.consumeExpected(JsonToken.END_ARRAY);
return bundleFiles;
}
protected DubConfiguration parseConfiguration(JsonReaderExt jsonReader)
throws IOException, DubBundleException {
jsonReader.consumeExpected(JsonToken.BEGIN_OBJECT);
String name = null;
String targetType = null;
String targetName = null;
String targetPath = null;
while(jsonReader.hasNext()) {
String propName = jsonReader.consumeExpectedPropName();
switch(propName) {
case "name":
name = jsonReader.consumeStringValue();
break;
case "targetType":
targetType = jsonReader.consumeStringValue();
break;
case "targetName":
targetName = jsonReader.consumeStringValue();
break;
case "targetPath":
targetPath = jsonReader.consumeStringValue();
break;
default:
jsonReader.skipValue();
}
}
jsonReader.consumeExpected(JsonToken.END_OBJECT);
if(name == null) {
throw new DubBundleException("Build configuration has no name attribute");
}
return new DubConfiguration(name, targetType, targetName, targetPath);
}
}