ObjectSchemaParser.java
package space.sunqian.fs.object.schema;
import space.sunqian.annotation.Nonnull;
import space.sunqian.annotation.RetainedParam;
import space.sunqian.annotation.ThreadSafe;
import space.sunqian.fs.cache.SimpleCache;
import space.sunqian.fs.collect.ListKit;
import space.sunqian.fs.object.schema.handlers.AbstractObjectSchemaHandler;
import space.sunqian.fs.object.schema.handlers.CommonSchemaHandler;
import space.sunqian.fs.object.schema.handlers.RecordSchemaHandler;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
/**
* This interface is used to parse {@link Type} to {@link ObjectSchema}. It uses a list of {@link Handler}s to execute
* the specific parsing operations, where each {@link Handler} possesses its own specific parsing logic.
* <p>
* There is a skeletal handler implementation: {@link AbstractObjectSchemaHandler}. And the default parser is based on
* {@link CommonSchemaHandler#getInstance()}.
*
* @author sunqian
*/
@ThreadSafe
public interface ObjectSchemaParser {
/**
* Returns the default {@link ObjectSchemaParser}. Here are handlers in the default parser:
* <ul>
* <li>
* {@link RecordSchemaHandler#getInstance()}, if the current JVM version supports {@code record} classes
* ({@code >= 16});
* </li>
* <li>{@link CommonSchemaHandler#getInstance()};</li>
* </ul>
* <p>
* Note the default {@link ObjectSchemaParser} is singleton, and never caches the parsed results.
*
* @return the default {@link ObjectSchemaParser}
* @see CommonSchemaHandler
* @see RecordSchemaHandler
*/
static @Nonnull ObjectSchemaParser defaultParser() {
return ObjectSchemaParserBack.defaultParser();
}
/**
* Returns the default cached {@link ObjectSchemaParser}, which is based on {@link #defaultParser()} and caches the
* parsed results with {@link SimpleCache#ofSoft()}.
* <p>
* Note the default cached {@link ObjectSchemaParser} is singleton.
*
* @return the default cached {@link ObjectSchemaParser}
* @see #defaultParser()
*/
static @Nonnull ObjectSchemaParser defaultCachedParser() {
return ObjectSchemaParserBack.defaultCachedParser();
}
/**
* Creates and returns a new {@link ObjectSchemaParser} with the given handlers.
* <p>
* Note the created {@link ObjectSchemaParser} never caches the parsed results.
*
* @param handlers the given handlers
* @return a new {@link ObjectSchemaParser} with the given handlers
*/
static @Nonnull ObjectSchemaParser newParser(@Nonnull @RetainedParam Handler @Nonnull ... handlers) {
return newParser(ListKit.list(handlers));
}
/**
* Creates and returns a new {@link ObjectSchemaParser} with given handlers.
* <p>
* Note the created {@link ObjectSchemaParser} never caches the parsed results.
*
* @param handlers given handlers
* @return a new {@link ObjectSchemaParser} with given handlers
*/
static @Nonnull ObjectSchemaParser newParser(@Nonnull @RetainedParam List<@Nonnull Handler> handlers) {
return ObjectSchemaParserBack.newParser(handlers);
}
/**
* Returns a new {@link ObjectSchemaParser} that caches the parsed results with the specified cache.
* <p>
* Note the behavior of the non-parsing methods of the returned {@link ObjectSchemaParser}, such as
* {@link #handlers()}, {@link #asHandler()} and {@link #withFirstHandler(Handler)}, will directly invoke the
* underlying {@link ObjectSchemaParser}.
*
* @param cache the specified cache to store the parsed results
* @param parser the underlying {@link ObjectSchemaParser} to parse the type
* @return a new {@link ObjectSchemaParser} that caches the parsed results with the specified cache
*/
static @Nonnull ObjectSchemaParser newCachedParser(
@Nonnull SimpleCache<@Nonnull Type, @Nonnull ObjectSchema> cache,
@Nonnull ObjectSchemaParser parser
) {
return ObjectSchemaParserBack.newCachedParser(cache, parser);
}
/**
* Parses the given type to an instance of {@link ObjectSchema}, and returns the parsed {@link ObjectSchema}.
*
* @param type the given type
* @return the parsed {@link ObjectSchema}
* @throws DataSchemaException if any problem occurs
* @implNote The default implementation of this method invokes the {@link Handler#parse(ObjectSchemaParser.Context)}
* in the order of {@link #handlers()} until one of the handlers returns {@code false}. The codes are similar to:
* <pre>{@code
* for (Handler handler : handlers()) {
* if (!handler.parse(context)) {
* break;
* }
* }
* }</pre>
*/
default @Nonnull ObjectSchema parse(@Nonnull Type type) throws DataSchemaException {
try {
ObjectSchemaBuilder builder = new ObjectSchemaBuilder(type);
for (Handler handler : handlers()) {
if (!handler.parse(builder)) {
break;
}
}
return builder.build(this);
} catch (Exception e) {
throw new DataSchemaException(type, e);
}
}
/**
* Returns all handlers of this {@link ObjectSchemaParser}.
*
* @return all handlers of this {@link ObjectSchemaParser}
*/
@Nonnull
List<@Nonnull Handler> handlers();
/**
* Returns a new {@link ObjectSchemaParser} of which first handler is the given handler and the next handler is this
* {@link ObjectSchemaParser} as a {@link Handler}. This method is equivalent:
* <pre>{@code
* newParser(firstHandler, this.asHandler())
* }</pre>
*
* @param firstHandler the first handler
* @return a new {@link ObjectSchemaParser} of which first handler is the given handler and the next handler is this
* {@link ObjectSchemaParser} as a {@link Handler}
*/
default @Nonnull ObjectSchemaParser withFirstHandler(@Nonnull Handler firstHandler) {
return newParser(firstHandler, this.asHandler());
}
/**
* Returns this {@link ObjectSchemaParser} as a {@link Handler}.
*
* @return this {@link ObjectSchemaParser} as a {@link Handler}
*/
@Nonnull
Handler asHandler();
/**
* Handler for {@link ObjectSchemaParser}, provides the specific parsing logic.
*
* @author sunqian
*/
@ThreadSafe
interface Handler {
/**
* Parses {@link Type} to {@link ObjectSchema} with its owner parsing logic. The {@link Type} is specified by
* {@link Context#parsedType()} of the given context, and the parsed properties should be stored in
* {@link Context#propertyBaseMap()}. Returns {@code false} to prevent subsequent handlers to continue to parse,
* otherwise returns {@code true} to continue to parse.
*
* @param context the given context
* @return whether to continue to parse
* @throws Exception for errors during parsing
*/
boolean parse(@Nonnull Context context) throws Exception;
}
/**
* Context for parsing the specified {@link Type} to {@link ObjectSchema}.
*
* @author sunqian
*/
interface Context {
/**
* Returns the type to be parsed.
*
* @return the type to be parsed
*/
@Nonnull
Type parsedType();
/**
* Returns a mutable map for storing property base infos.
* <p>
* The map through whole parsing process, stores and shares the property base infos for all handlers, and each
* handler can add or remove or modify the property base info.
*
* @return a mutable map for storing property base infos
*/
@Nonnull
Map<@Nonnull String, @Nonnull ObjectPropertyBase> propertyBaseMap();
}
}