// Copyright (C) 2006 Google Inc.
//
// 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 com.google.ical.values;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
* ical objects are made up of parameters (key=value pairs) and the contents
* are often one or more value types or key=value pairs.
* This schema encapsulates rules that can be applied to parse each part before
* inserting the results into the {@link IcalObject}.
*
* @author mikesamuel+svn@gmail.com (Mike Samuel)
*/
class IcalSchema {
/** rules for decoding parameter values */
private final Map<String, ParamRule> paramRules;
/** rules for decoding parts of the content body */
private final Map<String, ContentRule> contentRules;
/** rules for breaking the content body or parameters into parts */
private final Map<String, ObjectRule> objectRules;
/** rules for parsing value types */
private final Map<String, XformRule> xformRules;
/** list of productions that we're processing for debugging. */
private final List<String> ruleStack = new ArrayList<String>();
private static final Pattern EXTENSION_PARAM_NAME_RE =
Pattern.compile("^X-[A-Z0-9\\-]+$", Pattern.CASE_INSENSITIVE);
IcalSchema(Map<String, ParamRule> paramRules,
Map<String, ContentRule> contentRules,
Map<String, ObjectRule> objectRules,
Map<String, XformRule> xformRules) {
this.paramRules = paramRules;
this.contentRules = contentRules;
this.objectRules = objectRules;
this.xformRules = xformRules;
}
/////////////////////////////////
// Parser Core
/////////////////////////////////
public void applyParamsSchema(
String rule, Map<String, String> params, IcalObject out)
throws ParseException {
for (Map.Entry<String, String> param : params.entrySet()) {
String name = param.getKey();
applyParamSchema(rule, name, param.getValue(), out);
}
}
public void applyParamSchema(
String rule, String name, String value, IcalObject out)
throws ParseException {
// all elements are allowed extension parameters
if (EXTENSION_PARAM_NAME_RE.matcher(name).find()) {
out.getExtParams().put(name, value);
return;
}
// if not an extension, apply the rule
ruleStack.add(rule);
try {
(paramRules.get(rule)).apply(this, name, value, out);
} finally {
ruleStack.remove(ruleStack.get(ruleStack.size() - 1));
}
}
public void applyContentSchema(String rule, String content, IcalObject out)
throws ParseException {
ruleStack.add(rule);
try {
try {
(contentRules.get(rule)).apply(this, content, out);
} catch (NumberFormatException ex) {
badContent(content);
} catch (IllegalArgumentException ex) {
badContent(content);
}
} finally {
ruleStack.remove(ruleStack.get(ruleStack.size() - 1));
}
}
public void applyObjectSchema(
String rule, Map<String, String> params, String content, IcalObject out)
throws ParseException {
ruleStack.add(rule);
try {
(objectRules.get(rule)).apply(this, params, content, out);
} finally {
ruleStack.remove(ruleStack.get(ruleStack.size() - 1));
}
}
public Object applyXformSchema(String rule, String content)
throws ParseException {
ruleStack.add(rule);
try {
try {
return (xformRules.get(rule)).apply(this, content);
} catch (NumberFormatException ex) {
badContent(content);
} catch (IllegalArgumentException ex) {
badContent(content);
}
throw new AssertionError(); // badContent raises an exception
} finally {
ruleStack.remove(ruleStack.get(ruleStack.size() - 1));
}
}
/////////////////////////////////
// Parser Error Handling
/////////////////////////////////
public void badParam(String name, String value)
throws ParseException {
throw new ParseException("parameter " + name + " has bad value [[" +
value + "]] in " + ruleStack, 0);
}
public void badPart(String part, String msg) throws ParseException {
if (null != msg) { msg = " : " + msg; } else { msg = ""; }
throw new ParseException("cannot parse [[" + part + "]] in " +
ruleStack + msg, 0);
}
public void dupePart(String part) throws ParseException {
throw new ParseException(
"duplicate part [[" + part + "]] in " + ruleStack, 0);
}
public void missingPart(String partName, String content)
throws ParseException {
throw new ParseException("missing part " + partName + " from [[" + content +
"]] in " + ruleStack, 0);
}
public void badContent(String content) throws ParseException {
throw new ParseException(
"cannot parse content line [[" + content + "]] in " + ruleStack, 0);
}
/**
* rule applied to parse an entire content line after its been split into
* unparsed/unescaped parameters and unescaped content.
*/
public interface ObjectRule {
/**
* @param schema the schema used to provide further rules.
*/
public void apply(IcalSchema schema,
Map<String, String> params,
String content,
IcalObject target)
throws ParseException;
}
/** rule applied to an ical content line parameter. */
public interface ParamRule {
public void apply(
IcalSchema schema, String name, String value, IcalObject out)
throws ParseException;
}
/** rule applied to part of the ical content body. */
public interface ContentRule {
public void apply(IcalSchema schema, String content, IcalObject target)
throws ParseException;
}
/** rule applied to parse an ical data value from its string form. */
public interface XformRule {
public Object apply(IcalSchema schema, String content)
throws ParseException;
}
}