com.google.publicalerts.cap.CapUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.google.publicalerts.cap.CapUtil.java

Source

/*
 * Copyright (C) 2011 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.publicalerts.cap;

import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.EnumValueDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Message.Builder;
import com.google.protobuf.ProtocolMessageEnum;

import org.apache.commons.lang3.StringEscapeUtils;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * Utilities for dealing with transforming to and from CAP protos.
 *
 * @author shakusa@google.com (Steve Hakusa)
 */
public class CapUtil {

    private static final Map<String, String> ENUM_CASING_EXCEPTIONS = ImmutableMap.<String, String>builder()
            .put("VERY_LIKELY", "Very Likely").put("CBRNE", "CBRNE").put("UNKNOWN_URGENCY", "Unknown")
            .put("UNKNOWN_SEVERITY", "Unknown").put("UNKNOWN_CERTAINTY", "Unknown").build();

    private static final Pattern BASE_64_PATTERN = Pattern
            .compile("^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$");

    // First group matches greedily, second reluctantly, so that we can match
    // multiple tags one after the other
    private static final Pattern HTML_TAG_PATTERN = Pattern.compile("<([^\\s>]+)(.*?)>");

    /**
     * Returns a CAP field value for an enum field.
     * <p/>
     * Enum values in proto use C++ scoping rules, so 2 enums
     * of the same message can't have the same name. We work around
     * this limitation by using the name Value_EnumTypeName
     *
     * @param evd the enum value descriptor
     * @return a CAP field name for the enum field.
     */
    public static String getEnumValue(EnumValueDescriptor evd) {
        String value = ENUM_CASING_EXCEPTIONS.get(evd.getName());
        return value == null ? camelCase(evd.getName()) : value;
    }

    /**
     * Returns a CAP field value for an enum field.
     * <p/>
     * Enum values in proto use C++ scoping rules, so 2 enums
     * of the same message can't have the same name. We work around
     * this limitation by using the name Value_EnumTypeName
     *
     * @param e the enum value
     * @return a CAP field name for the enum field.
     */
    public static String getEnumValue(ProtocolMessageEnum e) {
        return getEnumValue(e.getValueDescriptor());
    }

    /**
     * Returns the CAP element name given a proto FieldDescriptor.
     * <p/>
     * This method takes care of differences in casing between CAP and proto.
     *
     * @param fd the field descriptor for which the name is returned
     * @return the element name of the field
     */
    public static String getElementName(FieldDescriptor fd) {
        return javaCase(fd.getName());
    }

    static String javaCase(String s) {
        return toCase(s, false);
    }

    static String camelCase(String s) {
        return toCase(s, true);
    }

    static String toCase(String s, boolean camel) {
        String[] parts = s.split("_");
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < parts.length; i++) {
            String part = parts[i];
            if (part.length() > 0) {
                sb.append(part.substring(0, 1).toUpperCase()).append(part.substring(1).toLowerCase());
            }
        }
        if (!camel && sb.length() > 0) {
            sb.replace(0, 1, String.valueOf(Character.toLowerCase(sb.charAt(0))));
        }
        return sb.toString();
    }

    /**
     * Returns a field descriptor for the field of the given builder with the
     * given CAP name. Takes care of differences in casing between CAP and proto.
     *
     * @param builder the builder to search in
     * @param name the CAP name to search for
     * @return the field, or null if it does not exist
     */
    public static FieldDescriptor findFieldByName(Builder builder, String name) {
        return builder == null ? null
                : builder.getDescriptorForType().findFieldByName(underscoreCase(name).toLowerCase());
    }

    static String underscoreCase(String s) {
        if (s.isEmpty()) {
            return s;
        }
        StringBuilder sb = new StringBuilder();
        char[] chars = s.toCharArray();
        sb.append(chars[0]);
        for (int i = 1; i < chars.length; i++) {
            char ch = chars[i];
            if (Character.isUpperCase(ch) && Character.isLowerCase(chars[i - 1])) {
                sb.append('_');
            }
            sb.append(ch);
        }
        return sb.toString();
    }

    /**
     * Returns a map of CAP fields to their child fields that may be repeated.
     * <p>
     * For instance, an "info" can have repeated "category" and "msgType" fields,
     * so both of those fields (and others) will be included in the set returned
     * by <code>getRepeatedFieldNames().get("info")</code>.
     *
     * @return a map of CAP fields to their child fields that may be repeated
     */
    public static Map<String, Set<String>> getRepeatedFieldNames() {
        Map<String, Set<String>> map = Maps.newHashMap();
        getRepeatedFieldNamesInternal(Alert.getDescriptor(), map);
        return map;
    }

    private static void getRepeatedFieldNamesInternal(Descriptor d, Map<String, Set<String>> result) {
        Set<String> repeatedFields = Sets.newHashSet();
        for (FieldDescriptor fd : d.getFields()) {
            if (fd.isRepeated()) {
                repeatedFields.add(javaCase(fd.getName()));
            }
            if (fd.getType() == FieldDescriptor.Type.MESSAGE) {
                getRepeatedFieldNamesInternal(fd.getMessageType(), result);
            }
        }
        if (!repeatedFields.isEmpty()) {
            result.put(javaCase(d.getName()), repeatedFields);
        }
    }

    /**
     * Returns true if the string is null or {@code s.trim()}
     * returns the empty string.
     *
     * @param s the string to check
     * @return true if the string is empty or whitespace
     */
    public static boolean isEmptyOrWhitespace(String s) {
        return Strings.nullToEmpty(s).trim().isEmpty();
    }

    /**
     * Returns {@code true} if the string represents base64-encoded content,
     * {@code false} otherwise.
     * 
     * <p>The input string cannot be {@code null}, but can be empty. In this
     * case, the function would return {@code true}.
     * 
     * <p>The new-line character '\n' is stripped.
     */
    public static boolean isBase64(String s) {
        return BASE_64_PATTERN.matcher(s.replace("\n", "")).find();
    }

    /**
     * Returns a string legal for use in a CAP {@literal <references>} parameter.
     */
    public static String formatCapReference(String capSender, String capIdentifier, Calendar sent) {
        return formatCapReference(capSender, capIdentifier, CapDateUtil.formatCapDate(sent));
    }

    /**
     * Returns a string legal for use in a CAP {@literal <references>} parameter.
     */
    public static String formatCapReference(String capSender, String capIdentifier, String sent) {
        // CAP reference format 'sender,identifier,sent'
        return String.format("%s,%s,%s", capSender, capIdentifier, sent);
    }

    /**
     * Parses a CAP identifier from a single valid reference in
     * {@literal <references>}.
     *
     * <p>Different parsing rules are applied according to the version of CAP
     * being employed.
     * 
     * <p>If the input string is not compliant with the XSD schema, and parsing it
     * is not possible, {@code null} is returned.
     */
    public static String parseReferenceIdentifier(String s, int capVersion) {
        if (capVersion <= 10) {
            int separatorIndex = s.indexOf('/');
            return (separatorIndex > 0) ? s.substring(0, separatorIndex) : null;
        }

        String[] parts = s.split(",", 3);
        return (parts.length > 1) ? parts[1] : null;
    }

    /**
     * Parses the date specified in a single valid reference in
     * {@literal <references>}.
     * 
     * <p>This method should be called only when parsing CAP feeds of version
     * 1.1 or greater.
     *
     * <p>If the input string is not compliant with the XSD schema, and parsing it
     * is not possible, {@code null} is returned.
     */
    public static Date parseReferenceSent(String s) {
        String[] parts = s.split(",", 3);
        return (parts.length > 2) ? CapDateUtil.toJavaDate(parts[2]) : null;
    }

    /**
     * Parses a string into a URI, as defined by RFC 2396.
     * 
     * <p>{@code null} is returned if the string does not match the spec.
     */
    public static URI parseUri(String s) {
        try {
            return new URI(s);
        } catch (URISyntaxException e) {
            return null;
        }
    }

    /**
     * Strips the XML preamble, if any, from the start of the given string.
     *
     * @param xmlDocument document to check for {@literal <?xml ..>} preamble.
     *     If a preamble exists, this method expects it to start on the first
     *     character of the given xmlDocument.
     * @return the same String, minus any preamble.
     */
    public static String stripXmlPreamble(String xmlDocument) {
        while (xmlDocument.startsWith("<?xml")) {
            xmlDocument = xmlDocument.substring(xmlDocument.indexOf(">") + 1);
        }
        return xmlDocument;
    }

    /**
     * @return {@true} if the input string contains HTML entities, {@code false}
     * otherwise
     */
    public static boolean containsHtmlEntities(String s) {
        return !StringEscapeUtils.unescapeHtml4(s).equals(s);
    }

    /**
     * @return {@true} if the input string contains HTML tags, {@code false}
     * otherwise
     */
    public static boolean containsHtmlTags(String s) {
        return HTML_TAG_PATTERN.matcher(s).find();
    }

    private CapUtil() {
    }
}