/**
* Copyright (c) 2013 Puppet Labs, Inc. and other contributors, as listed below.
* 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:
* Puppet Labs
*/
package com.puppetlabs.geppetto.forge.util;
import static com.puppetlabs.geppetto.diagnostic.Diagnostic.ERROR;
import static com.puppetlabs.geppetto.diagnostic.Diagnostic.WARNING;
import static com.puppetlabs.geppetto.forge.Forge.PACKAGE;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import org.codehaus.jackson.JsonLocation;
import org.codehaus.jackson.JsonParseException;
import com.puppetlabs.geppetto.diagnostic.Diagnostic;
import com.puppetlabs.geppetto.diagnostic.DiagnosticType;
import com.puppetlabs.geppetto.diagnostic.FileDiagnostic;
import com.puppetlabs.geppetto.forge.model.ModuleName;
import com.puppetlabs.geppetto.semver.Version;
import com.puppetlabs.geppetto.semver.VersionRange;
public abstract class MetadataJsonParser extends JsonPositionalParser {
public static final DiagnosticType METADATA_JSON = new DiagnosticType(
"METADATA_JSON", MetadataJsonParser.class.getName());
protected abstract void call(CallSymbol key, int line, int offset, int length, List<JElement> arguments);
protected Diagnostic createDiagnostic(JElement element, int severity, String message) {
FileDiagnostic diag = new FileDiagnostic(severity, METADATA_JSON, message, element.getFile());
diag.setLineNumber(element.getLine());
return diag;
}
protected ModuleName createModuleName(JElement jsonName, boolean dependency, Diagnostic chain) {
String moduleName = validateString(jsonName, CallSymbol.name.name(), chain);
if(moduleName == null)
return null;
try {
return ModuleName.create(moduleName, true);
}
catch(IllegalArgumentException e) {
try {
chain.addChild(createDiagnostic(jsonName, WARNING, getBadNameMessage(e, dependency)));
return ModuleName.create(moduleName, false);
}
catch(IllegalArgumentException e2) {
chain.addChild(createDiagnostic(jsonName, ERROR, getBadNameMessage(e, dependency)));
return null;
}
}
}
protected Version createVersion(JElement jsonVersion, Diagnostic chain) {
String version = validateString(jsonVersion, CallSymbol.version.name(), chain);
if(version == null)
return null;
try {
return Version.fromString(version);
}
catch(IllegalArgumentException e) {
chain.addChild(createDiagnostic(jsonVersion, ERROR, e.getMessage()));
return null;
}
}
protected VersionRange createVersionRequirement(JElement jsonVersionRequirement, Diagnostic chain) {
String versionRequirement = validateString(jsonVersionRequirement, "version_requirement", chain);
if(versionRequirement == null)
return null;
try {
return VersionRange.create(versionRequirement);
}
catch(IllegalArgumentException e) {
chain.addChild(createDiagnostic(jsonVersionRequirement, ERROR, e.getMessage()));
return null;
}
}
protected String getBadNameMessage(IllegalArgumentException e, boolean dependency) {
String pfx = dependency
? "A dependency "
: "A module ";
return pfx + e.getMessage();
}
public void parse(File file, String content, Diagnostic chain) throws JsonParseException, IOException {
JElement root = parse(file, content);
if(!(root instanceof JObject))
throw new JsonParseException("Excpected Json Object", JsonLocation.NA);
ModuleName fullName = null;
boolean nameSeen = false;
boolean versionSeen = false;
for(JEntry entry : ((JObject) root).getEntries()) {
try {
CallSymbol symbol = CallSymbol.valueOf(entry.getKey());
if(symbol == CallSymbol.dependency)
// Not recognized in metadata.json
throw new IllegalArgumentException();
JElement args = entry.getElement();
if(symbol == CallSymbol.name) {
fullName = createModuleName(args, false, chain);
nameSeen = true;
}
else if(symbol == CallSymbol.version) {
createVersion(args, chain);
versionSeen = true;
}
else
switch(symbol) {
case dependencies:
validateDependencies(args, symbol.name(), chain);
break;
case types:
validateTypes(args, symbol.name(), chain);
break;
case checksums:
validateChecksums(args, symbol.name(), chain);
break;
default:
validateString(args, symbol.name(), chain);
}
if(args instanceof JArray)
call(symbol, entry.getLine(), entry.getOffset(), entry.getLength(), ((JArray) args).getValues());
else
call(
symbol, entry.getLine(), entry.getOffset(), entry.getLength(),
Collections.singletonList(entry.getElement()));
}
catch(IllegalArgumentException e) {
chain.addChild(createDiagnostic(entry, ERROR, "Unrecognized call: " + entry.getKey()));
}
}
if(!nameSeen || fullName != null && (fullName.getOwner() == null || fullName.getName() == null)) {
chain.addChild(new FileDiagnostic(ERROR, PACKAGE, "A full name (user-module) must be specified", file));
}
if(!versionSeen) {
chain.addChild(new FileDiagnostic(ERROR, PACKAGE, "A version must be specified", file));
}
}
protected List<JElement> validateArray(JElement element, String symbol, Diagnostic chain) {
if(element instanceof JArray)
return ((JArray) element).getValues();
chain.addChild(createDiagnostic(element, ERROR, symbol + " must be an array"));
return Collections.emptyList();
}
private void validateChecksums(JElement args, String name, Diagnostic chain) {
// This is derived material. Hardly a need to validate
}
private void validateDependencies(JElement args, String name, Diagnostic chain) {
for(JElement dep : validateArray(args, name, chain)) {
for(JEntry entry : validateObject(dep, name, chain)) {
if("name".equals(entry.getKey()))
createModuleName(entry.getElement(), true, chain);
else if("version_requirement".equals(entry.getKey()) || "versionRequirement".equals(entry.getKey()))
createVersionRequirement(entry.getElement(), chain);
else
chain.addChild(createDiagnostic(entry, WARNING, "Unrecognized entry: " + entry.getKey()));
}
}
}
protected void validateNamedDocEntry(JElement elem, String name, Diagnostic chain) {
for(JEntry entry : validateObject(elem, name, chain)) {
if("name".equals(entry.getKey()) || "doc".equals(entry.getKey()))
validateString(entry.getElement(), entry.getKey(), chain);
else
chain.addChild(createDiagnostic(entry, WARNING, "Unrecognized entry: " + entry.getKey()));
}
}
protected List<JEntry> validateObject(JElement element, String symbol, Diagnostic chain) {
if(element instanceof JObject)
return ((JObject) element).getEntries();
chain.addChild(createDiagnostic(element, Diagnostic.ERROR, symbol + " must be an object"));
return Collections.emptyList();
}
protected String validateString(JElement element, String symbol, Diagnostic chain) {
String str = null;
if(element instanceof JPrimitive) {
Object value = ((JPrimitive) element).getValue();
if(value instanceof String)
str = (String) value;
}
if(str == null)
chain.addChild(createDiagnostic(element, ERROR, symbol + " must be a string"));
return str;
}
protected void validateTypes(JElement args, String name, Diagnostic chain) {
for(JElement dep : validateArray(args, name, chain)) {
for(JEntry entry : validateObject(dep, name, chain)) {
String key = entry.getKey();
if("name".equals(entry.getKey()) || "doc".equals(entry.getKey()))
validateString(entry.getElement(), entry.getKey(), chain);
else if("parameters".equals(key) || "providers".equals(key) || "properties".equals(key))
for(JElement param : validateArray(entry.getElement(), key, chain))
validateNamedDocEntry(param, key, chain);
else
chain.addChild(createDiagnostic(entry, WARNING, "Unrecognized entry: " + entry.getKey()));
}
}
}
}