/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.felix.useradmin.mongodb; /** * Provides a small utility class for encoding/decoding keys used directly in * MongoDB. * <p> * MongoDB does not allow keys to have '.' and '$' (amongst others?) in their * name, so we need to ensure that those characters are somehow encoded. * </p> * * @see http://www.mongodb.org/display/DOCS/Legal+Key+Names * @see http://www.yoyobrain.com/flashcards/show/261984 */ final class KeyCodec { private static final String ENCODED_NULL = "%00"; private static final String ENCODED_UNDERSCORE = "%5F"; private static final String ENCODED_DOT = "%2E"; private static final String ENCODED_DOLLAR = "%24"; /** * Encodes a given key by replacing all '.' and '$' with their URL encoded variants. * * @param input the input to encode, may be <code>null</code>. * @return the encoded input, can be <code>null</code>. */ public static String encode(String input) { if (input == null) { return null; } StringBuilder sb = new StringBuilder(input.length()); for (int i = 0; i < input.length(); i++) { char c = input.charAt(i); if (c == 0) { sb.append(ENCODED_NULL); } else if (c == '_') { sb.append(ENCODED_UNDERSCORE); } else if (c == '.') { sb.append(ENCODED_DOT); } else if (c == '$') { sb.append(ENCODED_DOLLAR); } else if (c == '%') { // escape all '%' as well... sb.append("%%"); } else { sb.append((char) c); } } return sb.toString(); } /** * Decodes a given key by replacing all URL encoded '.' and '$' entities with their real characters. * * @param input the input to decode, may be <code>null</code>. * @return the decoded input, can be <code>null</code>. */ public static String decode(String input) { if (input == null) { return null; } boolean percentSeen = false; int length = input.length(); char oldC = input.charAt(0); StringBuilder sb = new StringBuilder(input.length()); for (int i = 1; i < length; i++) { char c = input.charAt(i); if (oldC == '%') { if (c == '%') { // Escaped percent... sb.append("%"); c = 0; // erase percent... percentSeen = false; } else { percentSeen = true; } } else if (c == '%') { percentSeen = true; } else if (percentSeen) { if (oldC == '0' && c == '0') { // Encoded null-character sb.append((char) 0); percentSeen = false; } else if (oldC == '2' && c == '4') { // Encoded dollar... sb.append("$"); percentSeen = false; } else if (oldC == '2' && c == 'E') { // Encoded dot... sb.append("."); percentSeen = false; } else if (oldC == '5' && c == 'F') { // Encoded underscore... sb.append("_"); percentSeen = false; } else { // Unknown encoded entity... sb.append("%").append(oldC).append(c); percentSeen = false; } } else { if (i == 1) { sb.append(oldC); } sb.append(c); } if (percentSeen && (i == length - 1)) { // At the end; incomplete entity found... if (oldC != '%' && c != '%') { sb.append("%").append(oldC).append(c); } else if (oldC == '%' && c != '%') { sb.append("%").append(c); } else if (c == '%') { sb.append("%"); } } oldC = c; } return sb.toString(); } }