Java tutorial
/** * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2012-2015 ForgeRock AS. All Rights Reserved * * The contents of this file are subject to the terms * of the Common Development and Distribution License * (the License). You may not use this file except in * compliance with the License. * * You can obtain a copy of the License at * http://forgerock.org/license/CDDLv1.0.html * See the License for the specific language governing * permission and limitations under the License. * * When distributing Covered Code, include this CDDL * Header Notice in each file and include the License file * at http://forgerock.org/license/CDDLv1.0.html * If applicable, add the following below the CDDL Header, * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" */ package org.forgerock.openidm.util; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import org.forgerock.json.JsonValue; import org.forgerock.openidm.core.ServerConstants; import org.joda.time.DateTime; import org.joda.time.Days; import org.joda.time.Hours; import org.joda.time.Minutes; import org.joda.time.Months; import org.joda.time.ReadablePeriod; import org.joda.time.Seconds; import org.joda.time.Years; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public final class ConfigMacroUtil { /** * Setup logging for the {@link ConfigMacroUtil}. */ private final static Logger logger = LoggerFactory.getLogger(ConfigMacroUtil.class); private final static DateUtil DATE_UTIL = DateUtil.getDateUtil(ServerConstants.TIME_ZONE_UTC); private ConfigMacroUtil() { } /** * Expands any interpolation contained within the JsonValue object in-place. * * @param json * JsonValue to parse for macros */ public static void expand(JsonValue json) { Iterator<String> iter = json.keys().iterator(); while (iter.hasNext()) { String key = iter.next(); String expanded = parse(json.get(key)); if (expanded != null) { json.put(key, expanded); } } } /** * Start the string parsing. If base is not a string, will return null. * * @param base * base JsonValue object to begin parsing from * @return a string with any interpolation expanded or null if base is not a * string */ private static String parse(JsonValue base) { if (!base.isString()) { return null; } return buildString(base.asString()); } /** * Begins building the string from interpolation and normal string contents. * * @param str * string to interpolate from * @return a string after interpolation */ public static String buildString(String str) { StringBuilder builder = new StringBuilder(); List<Integer> possibleLocations = possibleLocations(str); if (possibleLocations.isEmpty()) { return null; } List<Integer[]> confirmedLocations = confirmedLocations(str, possibleLocations); if (confirmedLocations.isEmpty()) { return null; } int lastEnd = 0; for (Integer[] pair : confirmedLocations) { int start = pair[0]; int length = pair[1]; int end = start + length; builder.append(str.substring(lastEnd, start)); builder.append(interpolate(str.substring(start, end))); lastEnd = pair[0] + pair[1]; } return builder.toString(); } /** * Identifies any possible interpolation locations (begins by looking for * "${"). * * @param str * string to look through for interpolation sites * @return list of indices where the interpolation sites begin */ private static List<Integer> possibleLocations(String str) { List<Integer> possibleLocations = new ArrayList<Integer>(); int lastIndex = -1; int index; while ((index = str.indexOf("${", lastIndex + 1)) >= 0) { if (lastIndex == index) { break; } possibleLocations.add(index); lastIndex = index; } return possibleLocations; } /** * Confirm interpolation sites by looking for a closing brace. * * @param str * string to confirm interpolation sites from * @param possibleLocations * list of string indicies indicating possible interpolation * beginnings * @return list paired integers containing (starting location, length of * interpolation string) */ private static List<Integer[]> confirmedLocations(String str, List<Integer> possibleLocations) { List<Integer[]> confirmedLocations = new ArrayList<Integer[]>(); Integer[] lastPair = { -1, -1 }; for (Integer start : possibleLocations) { int length = 0; // Ignore any escaped \${} if (start != 0 && str.charAt(start - 1) == '\\') { continue; } // Determine the length and existence of a ${} block boolean found = false; for (int i = start; i < str.length(); i++) { length += 1; if (str.charAt(i) == '}') { found = true; break; } } // Don't add overlapping pairs -- this will keep "${ ${hi} }" from // being interpolated // technically it will wind up "${ ${hi}" if ((lastPair[0] + lastPair[1] < start) && found) { Integer[] pair = { start, length }; confirmedLocations.add(pair); } } return confirmedLocations; } /** * Interpolates the macros contained in the interpolation braces. * * <b>NOTE:</b> for ease of tokenization, this expects each token to have a * space between each component <b><i>e.g.</i></b> "Time.now + 1d" rather * than "Time.now+1d" <br> * <br> * <b>TODO:</b> Proper tokenizing * * @param str * interpolation string * @return interpolated string */ private static String interpolate(String str) { String toInterpolate = str.substring(2, str.length() - 1); // Strip ${ // and } List<String> tokens = Arrays.asList(toInterpolate.split(" ")); StringBuilder builder = new StringBuilder(); Iterator<String> iter = tokens.iterator(); while (iter.hasNext()) { String token = iter.next(); if (token.equals("Time.now")) { builder.append(handleTime(tokens, iter)); } else { logger.warn("Unrecognized token: {}", token); builder.append(token); } } return builder.toString(); } /** * Handles the Time.now macro * * @param tokens * list of tokens * @param iter * iterator used to iterate over the list of tokens * @return string containing the interpolated time token */ private static String handleTime(List<String> tokens, Iterator<String> iter) { DateTime dt = new DateTime(); // Add some amount if (iter.hasNext()) { String operationToken = iter.next(); if (operationToken.equals("+") || operationToken.equals("-")) { if (iter.hasNext()) { String quantityToken = iter.next(); // Get the magnitude to // add or subtract ReadablePeriod period = getTimePeriod(quantityToken); if (operationToken.equals("-")) { dt = dt.minus(period); } else { dt = dt.plus(period); } } else { logger.warn("Token '{}' not followed by a quantity", operationToken); } } else { logger.warn("Invalid token '{}', must be operator '+' or '-'", operationToken); } } return DATE_UTIL.formatDateTime(dt); } /** * Defines the magnitudes that can be added to the timestamp * * @param token * token of form "[number][magnitude]" (ex. "1d") * @return integer indicating the magnitude of the date for the calendar * system */ public static ReadablePeriod getTimePeriod(String token) { String valString = token.substring(0, token.length() - 1); int value = Integer.parseInt(valString); char mag = token.charAt(token.length() - 1); ReadablePeriod period; switch (mag) { case 's': period = Seconds.seconds(value); break; case 'm': period = Minutes.minutes(value); break; case 'h': period = Hours.hours(value); break; case 'd': period = Days.days(value); break; case 'M': period = Months.months(value); break; case 'y': period = Years.years(value); break; default: logger.warn("Invalid date magnitude: {}. Defaulting to seconds.", mag); period = Seconds.seconds(value); break; } return period; } }