io.protostuff.parser.Proto Maven / Gradle / Ivy
//========================================================================
//Copyright 2007-2009 David Yu [email protected]
//------------------------------------------------------------------------
//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 io.protostuff.parser;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Contains the metadata of parsed protos. Basically repesents the .proto file.
*
* @author David Yu
* @created Dec 18, 2009
*/
public class Proto extends AnnotationContainer implements HasOptions
{
final File file;
// if loaded form classpath.
final URL url;
final Loader loader;
final Proto importer;
Mutable packageName, javaPackageName;
final LinkedHashMap importedProtos = new LinkedHashMap();
final LinkedHashMap standardOptions = new LinkedHashMap();
final LinkedHashMap extraOptions = new LinkedHashMap();
final LinkedHashMap messages = new LinkedHashMap();
final LinkedHashMap enumGroups = new LinkedHashMap();
final LinkedHashMap services = new LinkedHashMap();
final ArrayList extensions = new ArrayList();
final LinkedHashMap fullyQualifiedMessages = new LinkedHashMap();
final LinkedHashMap fullyQualifiedEnumGroups = new LinkedHashMap();
// from options and annotations
final ArrayList references = new ArrayList();
int refOffset;
private String sourcePath;
public Proto()
{
this((File) null, DefaultProtoLoader.DEFAULT_INSTANCE, null);
}
public Proto(File file)
{
this(file, DefaultProtoLoader.DEFAULT_INSTANCE, null);
}
public Proto(Loader loader)
{
this((File) null, loader, null);
}
public Proto(File file, Loader loader)
{
this(file, loader, null);
}
public Proto(File file, Loader loader, Proto importer)
{
this.url = null;
this.file = file;
this.loader = loader;
this.importer = importer;
}
public Proto(URL url, Loader loader, Proto importer)
{
this.file = null;
this.url = url;
this.loader = loader;
this.importer = importer;
}
@Override
public Proto getProto()
{
return this;
}
public ErrorMap getError()
{
return ErrorMap.INSTANCE;
}
public File getFile()
{
return file;
}
public String getSourcePath()
{
if (sourcePath == null)
sourcePath = file == null ? String.valueOf(url) : file.toString();
return sourcePath;
}
public Mutable getMutablePackageName()
{
return packageName;
}
public String getPackageName()
{
return packageName == null ? null : packageName.getValue();
}
/**
* Returns the package name that was configured in the proto. Note that {@link #getPackageName()} will have the same
* value as this, if the compiler options did not have entries that override it.
*/
public String getOriginalPackageName()
{
if (packageName == null)
return null;
String original = packageName.getLast();
return original != null ? original : packageName.getValue();
}
public Mutable getMutableJavaPackageName()
{
return javaPackageName;
}
public String getJavaPackageName()
{
return javaPackageName.getValue();
}
/**
* Returns the java package name that was configured in the proto. Note that {@link #getJavaPackageName()} will have
* the same value as this, if the compiler options did not have entries that override it.
*/
public String getOriginalJavaPackageName()
{
if (javaPackageName == null)
return null;
String original = javaPackageName.getLast();
return original != null ? original : javaPackageName.getValue();
}
void setPackageName(String packageName)
{
if (this.packageName == null)
this.packageName = new Mutable(packageName);
}
public LinkedHashMap getStandardOptions()
{
return standardOptions;
}
public Object getStandardOption(String name)
{
return standardOptions.get(name);
}
public LinkedHashMap getExtraOptions()
{
return extraOptions;
}
public LinkedHashMap getO()
{
return getOptions();
}
@Override
public LinkedHashMap getOptions()
{
return extraOptions;
}
@Override
public void putStandardOption(String key, Object value)
{
putExtraOption(key, value);
standardOptions.put(key, value);
}
@Override
public void putExtraOption(String key, Object value)
{
if (extraOptions.put(key, value) != null)
throw err("Duplicate proto option: " + key, this);
}
@SuppressWarnings("unchecked")
public V getExtraOption(java.lang.String key)
{
return (V) extraOptions.get(key);
}
public Map getMessageMap()
{
return messages;
}
public Collection getMessages()
{
return messages.values();
}
public Message getMessage(String name)
{
return messages.get(name);
}
void addMessage(Message message)
{
if (messages.put(message.name, message) != null)
throw err("Duplicate message: " + message.name, this);
}
public Map getEnumGroupMap()
{
return enumGroups;
}
public Collection getEnumGroups()
{
return enumGroups.values();
}
public EnumGroup getEnumGroup(String name)
{
return enumGroups.get(name);
}
void addEnumGroup(EnumGroup enumGroup)
{
if (enumGroups.put(enumGroup.name, enumGroup) != null)
throw err("Duplicate enum: " + enumGroup.name, this);
}
public Map getServiceMap()
{
return services;
}
public Collection getServices()
{
return services.values();
}
public Service getService(String name)
{
return services.get(name);
}
void addService(Service service)
{
if (services.put(service.name, service) != null)
throw err("Duplicate service: " + service.name, this);
}
public void addExtension(Extension extension)
{
extensions.add(extension);
}
public Collection getExtensions()
{
return extensions;
}
public Collection getImportedProtos()
{
return importedProtos.values();
}
public Proto getImportedProto(File file)
{
return importedProtos.get(file.toURI().toString());
}
public Proto getImportedProto(URL url)
{
return importedProtos.get(url.toString());
}
public Proto getImportedProto(String url)
{
return importedProtos.get(url);
}
void importProto(String path)
{
try
{
addImportedProto(loader.load(path, this));
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
void addImportedProto(Proto proto)
{
if (proto.url == null)
importedProtos.put(proto.file.toURI().toString(), proto);
else
importedProtos.put(proto.url.toString(), proto);
}
void postParse()
{
if (packageName == null)
{
setPackageName("");
}
String javaPkg = (String) extraOptions.get("java_package");
String javaPackageName = javaPkg == null || javaPkg.length() == 0 ?
packageName.getValue() : javaPkg;
this.javaPackageName = new Mutable(javaPackageName);
// Cache all fully-qualified message names and enum group names so we
// can look them up quickly
for (Message m : getMessages())
m.cacheFullyQualifiedNames();
for (EnumGroup eg : getEnumGroups())
eg.cacheFullyQualifiedName();
for (Message m : getMessages())
m.resolveReferences(m);
for (Service s : getServices())
s.resolveReferences();
for (Extension e : getExtensions())
e.resolveReferences();
for (ConfiguredReference r : references)
r.resolve(this);
if (!standardOptions.isEmpty())
ConfiguredReference.resolve(this, standardOptions, extraOptions, getPackageName());
}
@Override
public void add(Annotation annotation)
{
super.add(annotation);
if (!annotation.refs.isEmpty())
references.add(new ConfiguredReference(annotation.refs, annotation.params, null));
}
void addAnnotationsTo(Message target)
{
if (target.addAnnotations(annotations, true))
{
if (refOffset != references.size())
{
String enclosingNamespace = target.getFullName();
int size = references.size();
while (refOffset < size)
references.get(refOffset++).enclosingNamespace = enclosingNamespace;
}
}
if (!docs.isEmpty())
{
target.docs.addAll(docs);
docs.clear();
}
}
void addAnnotationsTo(EnumGroup target)
{
if (target.addAnnotations(annotations, true))
{
if (refOffset != references.size())
{
String enclosingNamespace = target.getFullName();
int size = references.size();
while (refOffset < size)
references.get(refOffset++).enclosingNamespace = enclosingNamespace;
}
}
if (!docs.isEmpty())
{
target.docs.addAll(docs);
docs.clear();
}
}
void addAnnotationsTo(EnumGroup.Value target)
{
if (target.addAnnotations(annotations, true))
{
if (refOffset != references.size())
{
String enclosingNamespace = target.getEnumGroup().getFullName();
int size = references.size();
while (refOffset < size)
references.get(refOffset++).enclosingNamespace = enclosingNamespace;
}
}
if (!docs.isEmpty())
{
target.docs.addAll(docs);
docs.clear();
}
}
void addAnnotationsTo(Field> target, String enclosingNamespace)
{
if (target.addAnnotations(annotations, true))
{
if (refOffset != references.size())
{
int size = references.size();
while (refOffset < size)
references.get(refOffset++).enclosingNamespace = enclosingNamespace;
}
}
if (!docs.isEmpty())
{
target.docs.addAll(docs);
docs.clear();
}
}
void addAnnotationsTo(Service target)
{
// enclosingNamespace not necessary
if (target.addAnnotations(annotations, true))
refOffset = references.size();
if (!docs.isEmpty())
{
target.docs.addAll(docs);
docs.clear();
}
}
void addAnnotationsTo(Service.RpcMethod target)
{
// enclosingNamespace not necessary
if (target.addAnnotations(annotations, true))
refOffset = references.size();
if (!docs.isEmpty())
{
target.docs.addAll(docs);
docs.clear();
}
}
void addAnnotationsTo(Extension target)
{
if (target.addAnnotations(annotations, true))
{
if (refOffset != references.size())
{
String enclosingNamespace = target.getEnclosingNamespace();
int size = references.size();
while (refOffset < size)
references.get(refOffset++).enclosingNamespace = enclosingNamespace;
}
}
if (!docs.isEmpty())
{
target.docs.addAll(docs);
docs.clear();
}
}
/**
* Given the name of a Message/EnumGroup reference and the namespace enclosing that reference (can be a full message
* name or package name), returns the referenced object if it exists.
*
* @param fullRefName
* The full name of the object as specified by the reference, including a package name if it was
* specified
* @param fullEnclosingNamespace
* The full enclosing namespace of the reference
* @return A Message or EnumGroup instance, or null
*/
HasName findReference(String fullRefName, String enclosingNamespace)
{
boolean doneSearch = false;
// Special case: if fullRefName begins with a period it is a fully qualified
// name so just search for that (but strip off the initial dot!)
if (fullRefName.charAt(0) == '.')
return findFullyQualifiedObject(fullRefName.substring(1));
// Search for the object in the enclosing namespace, as well as each parent
// namespace until we find the object
while (!doneSearch)
{
String searchName = (enclosingNamespace == null ? fullRefName : enclosingNamespace + '.' + fullRefName);
HasName obj = findFullyQualifiedObject(searchName);
if (obj != null)
return obj;
// We didn't find the object. Strip off the last component of the
// namespace and try again
if (enclosingNamespace != null)
{
int dotIndex = enclosingNamespace.lastIndexOf('.');
if (dotIndex < 0)
enclosingNamespace = null;
else
enclosingNamespace = enclosingNamespace.substring(0, dotIndex);
}
else
{
doneSearch = true;
}
}
// If we didn't find a match using the normal protobuf-compatible
// lookup method, try searching each imported .proto as if we were
// in their namespace
for (Proto proto : getImportedProtos())
{
String searchName = (proto.getPackageName() == null ? fullRefName : proto.getPackageName() + '.'
+ fullRefName);
HasName result = proto.findFullyQualifiedObject(searchName);
if (result != null)
return result;
}
// No results
return null;
}
Message findMessageReference(String fullRefName, String enclosingNamespace)
{
HasName refObj = findReference(fullRefName, enclosingNamespace);
if (refObj instanceof Message)
return (Message) refObj;
else
return null;
}
EnumGroup findEnumGroupReference(String fullRefName, String enclosingNamespace)
{
HasName refObj = findReference(fullRefName, enclosingNamespace);
if (refObj instanceof EnumGroup)
return (EnumGroup) refObj;
else
return null;
}
/**
* Returns a Message or EnumGroup given its fully qualified name
*
* @param fullyQualifiedName
* The fully qualified name, without an initial dot ('.')
* @return The Message or EnumGroup instance if it is defined in this Proto or one of its imports.
*/
HasName findFullyQualifiedObject(String fullyQualifiedName)
{
Message m = fullyQualifiedMessages.get(fullyQualifiedName);
if (m != null)
return m;
EnumGroup eg = fullyQualifiedEnumGroups.get(fullyQualifiedName);
if (eg != null)
return eg;
// Search imported protos as well
for (Proto proto : getImportedProtos())
{
HasName importedObj = proto.findFullyQualifiedObject(fullyQualifiedName);
if (importedObj != null)
return importedObj;
}
return null;
}
public String toString()
{
return new StringBuilder()
.append('{')
.append("packageName:").append(packageName)
.append(',').append("standardOptions:").append(standardOptions)
.append(',').append("extraOptions:").append(extraOptions)
.append(',').append("messages:").append(getMessages())
.append('}')
.toString();
}
public interface Loader
{
public Proto load(String path, Proto importer) throws Exception;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy