
io.protostuff.runtime.RuntimeView Maven / Gradle / Ivy
Show all versions of protostuff-runtime-view Show documentation
//========================================================================
//Copyright 2012 David Yu
//------------------------------------------------------------------------
//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.runtime;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import io.protostuff.Input;
import io.protostuff.Output;
import io.protostuff.Schema;
import io.protostuff.runtime.RuntimeEnv.Instantiator;
/**
* A view schema can choose which fields to include during ser/deser.
*
* @author David Yu
* @created Nov 9, 2012
*/
public final class RuntimeView
{
private RuntimeView()
{
}
/**
* Returns a new view schema based from an existing one.
*/
public static Schema createFrom(RuntimeSchema rs,
Factory vf, Predicate.Factory pf,
String... args)
{
return createFrom(rs, rs.instantiator, vf, pf, args);
}
/**
* Returns a new view schema based from an existing one.
*/
public static Schema createFrom(RuntimeSchema ms,
Instantiator instantiator,
Factory vf,
Predicate.Factory pf,
String... args)
{
return vf.create(ms, instantiator, pf, args);
}
public interface Factory
{
/**
* Creates a view schema based from the given metadata.
*
* @param pf
* is optional, depending on the view factory used.
* @param args
* is optional, depending on the view factory used.
*/
Schema create(RuntimeSchema ms, Instantiator instantiator, Predicate.Factory pf, String[] args);
}
/**
* The base schema used by the built-in factories.
*/
public static abstract class BaseSchema implements Schema
{
public final Class super T> typeClass;
public final Instantiator instantiator;
protected BaseSchema(Class super T> typeClass, Instantiator instantiator)
{
this.typeClass = typeClass;
this.instantiator = instantiator;
}
@Override
public Class super T> typeClass()
{
return typeClass;
}
@Override
public String messageName()
{
return typeClass.getSimpleName();
}
@Override
public String messageFullName()
{
return typeClass.getName();
}
@Override
public boolean isInitialized(T message)
{
// same as runtime schema
return true;
}
@Override
public T newMessage()
{
return instantiator.newInstance();
}
}
public static abstract class PostFilteredSchema extends BaseSchema
{
public final Field[] fields;
protected PostFilteredSchema(Class super T> typeClass, Instantiator instantiator,
Field[] fields)
{
super(typeClass, instantiator);
this.fields = fields;
}
}
/**
* Built-in view schema factories.
*
*
*
* All factory args are required except:
*
* {@link Predicate.Factory} pf
* {@link String}[] args
*
*
* For application/behavior specific filters, create your own view factory or predicate factory, and then design an
* ahead-of-time filter (which is usually done at application startup).
*/
public enum Factories implements Factory
{
/**
* Filters the fields to include based on a {@link Predicate}.
*
* The {@link Predicate.Factory} arg is required.
*/
PREDICATE
{
@Override
public Schema create(final RuntimeSchema ms, Instantiator instantiator, Predicate.Factory pf,
String[] args)
{
if (pf == null)
throw new IllegalArgumentException("Predicate.Factory arg must not be null.");
final Predicate predicate = pf.create(args);
return new BaseSchema(ms.typeClass(), instantiator)
{
@Override
public int getFieldNumber(String name)
{
final Field field = ms.getFieldByName(name);
return field != null && predicate.apply(field) ? field.number : 0;
}
@Override
public void mergeFrom(Input input, T message) throws IOException
{
for (int number = input.readFieldNumber(this); number != 0;
number = input.readFieldNumber(this))
{
final Field field = ms.getFieldByNumber(number);
if (field == null || !predicate.apply(field, message))
input.handleUnknownField(number, this);
else
field.mergeFrom(input, message);
}
}
@Override
public String getFieldName(int number)
{
// only called during writes
// the predicate already applied on writeTo (the method below)
final Field field = ms.getFieldByNumber(number);
return field == null ? null : field.name;
}
@Override
public void writeTo(Output output, T message) throws IOException
{
for (Field f : ms.getFields())
{
if (predicate.apply(f, message))
f.writeTo(output, message);
}
}
};
}
},
/**
* Exclude the fields for merging and writing.
*
* The args param is required (the field names to exclude) if {@link Predicate.Factory} is not provided.
*/
EXCLUDE
{
@Override
public Schema create(final RuntimeSchema ms, Instantiator instantiator,
Predicate.Factory factory, String[] args)
{
final HashMap> fieldsByName = factory == null ?
copyAndExclude(ms.typeClass(), ms.getFields(), args) :
copyAndExclude(ms.typeClass(), ms.getFields(), factory.create(args));
@SuppressWarnings("unchecked")
Field[] fields = (Field[]) new Field>[fieldsByName.size()];
int i = 0;
for (Field field : fieldsByName.values())
{
fields[i++] = field;
}
return new PostFilteredSchema(ms.typeClass(), instantiator, fields)
{
@Override
public int getFieldNumber(String name)
{
final Field field = fieldsByName.get(name);
return field == null ? 0 : field.number;
}
@Override
public void mergeFrom(Input input, T message) throws IOException
{
for (int number = input.readFieldNumber(this); number != 0;
number = input.readFieldNumber(this))
{
final Field field = ms.getFieldByNumber(number);
if (field == null || !fieldsByName.containsKey(field.name))
input.handleUnknownField(number, this);
else
field.mergeFrom(input, message);
}
}
@Override
public String getFieldName(int number)
{
// only called during writes
final Field field = ms.getFieldByNumber(number);
return field == null ? null : field.name;
}
@Override
public void writeTo(Output output, T message) throws IOException
{
for (Field f : fields)
f.writeTo(output, message);
}
};
}
},
/**
* @deprecated use {@link io.protostuff.runtime.RuntimeView.Factories#EXCLUDE}
*/
EXCLUDE_OPTIMIZED_FOR_MERGE_ONLY {
@Override
public Schema create(RuntimeSchema ms, Instantiator instantiator, Predicate.Factory pf, String[] args)
{
return EXCLUDE.create(ms, instantiator, pf, args);
}
},
/**
* Include the fields for merging and writing.
*
* The args param is required (the field names to include).
*/
INCLUDE
{
@Override
public Schema create(final RuntimeSchema ms, Instantiator instantiator,
Predicate.Factory factory, String[] args)
{
final HashMap> fieldsByName =
new HashMap<>();
int maxFieldNumber = includeAndAddTo(fieldsByName, ms.typeClass(), ms.getFields(), args);
@SuppressWarnings("unchecked")
Field[] fields = new Field[fieldsByName.size()];
int i = 0;
for (Field field : fieldsByName.values())
{
fields[i++] = field;
}
return new PostFilteredSchema(ms.typeClass(), instantiator, fields)
{
@Override
public int getFieldNumber(String name)
{
final Field field = fieldsByName.get(name);
return field == null ? 0 : field.number;
}
@Override
public void mergeFrom(Input input, T message) throws IOException
{
for (int number = input.readFieldNumber(this); number != 0;
number = input.readFieldNumber(this))
{
final Field field = ms.getFieldByNumber(number);
if (field == null || !fieldsByName.containsKey(field.name))
input.handleUnknownField(number, this);
else
field.mergeFrom(input, message);
}
}
@Override
public String getFieldName(int number)
{
// only called during writes
final Field field = ms.getFieldByNumber(number);
return field == null ? null : field.name;
}
@Override
public void writeTo(Output output, T message) throws IOException
{
for (Field f : fields)
f.writeTo(output, message);
}
};
}
},
/**
* @deprecated use {@link io.protostuff.runtime.RuntimeView.Factories#INCLUDE}
*/
INCLUDE_OPTIMIZED_FOR_MERGE_ONLY {
@Override
public Schema create(RuntimeSchema ms, Instantiator instantiator, Predicate.Factory pf, String[] args)
{
return INCLUDE.create(ms, instantiator, pf, args);
}
};
}
static HashMap> copyAndExclude(Class super T> typeClass,
List> fields, final Predicate predicate)
{
final HashMap> map = new HashMap<>();
for (Field field : fields)
{
if (!predicate.apply(field))
{
map.put(field.name, field);
}
}
return map;
}
static HashMap> copyAndExclude(Class super T> typeClass,
List> fields, final String[] args)
{
if (args == null || args.length == 0)
throw new IllegalArgumentException("You must provide at least 1 field to exclude.");
HashMap> map = new HashMap<>();
Set exclude = new HashSet<>();
Collections.addAll(exclude, args);
for (Field field : fields)
{
if (!exclude.contains(field.name))
{
map.put(field.name, field);
}
}
return map;
}
static int includeAndAddTo(Map> map,
Class super T> typeClass, List> fields, final String[] args)
{
if (args == null || args.length == 0)
throw new IllegalArgumentException("You must provide at least 1 field to include.");
int maxFieldNumber = 0;
Set include = new HashSet<>();
Collections.addAll(include, args);
for (Field field : fields)
{
if (include.contains(field.name))
{
map.put(field.name, field);
maxFieldNumber = Math.max(field.number, maxFieldNumber);
}
}
return maxFieldNumber;
}
}