Form.java

/**
 * Copyright (C) 2017 HttpBuilder-NG Project
 *
 * 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 groovyx.net.http;

import java.util.Map;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ArrayList;
import java.io.InputStream;
import java.io.Reader;
import java.io.InputStreamReader;
import java.net.URLEncoder;
import java.net.URLDecoder;
import java.io.UnsupportedEncodingException;
import java.io.IOException;
import java.util.Collections;
import java.nio.charset.Charset;

public class Form {

    private static class Buffers {
        final StringBuilder builder = new StringBuilder(1024);
        final char[] buffer = new char[1024];

        public void clear() {
            builder.setLength(0);
        }
    }

    private static ThreadLocal<Buffers> tlBuffers = new ThreadLocal<Buffers>() {
            @Override protected Buffers initialValue() {
                return new Buffers();
            }
        };

    private static void toBuffers(final InputStream is, final Charset charset) throws IOException {
        tlBuffers.get().clear();
        final Reader reader = new InputStreamReader(is, charset);
        final StringBuilder builder = tlBuffers.get().builder;
        final char[] buffer = tlBuffers.get().buffer;
        
        int total = 0;
        while((total = reader.read(buffer, 0, 1024)) != -1) {
            builder.append(buffer, 0, total);
        }
    }
    
    public static Map<String,List<String>> decode(final InputStream is, final Charset charset) {
        try {
            toBuffers(is, charset);
            return decode(tlBuffers.get().builder, charset);
        }
        catch(IOException e) {
            throw new TransportingException("Error in decoding form", e);
        }
    }

    public static Map<String,List<String>> decode(final StringBuilder builder, final Charset charset) throws IOException {
        final Map<String,List<String>> ret = new LinkedHashMap<>();
        int lower = 0;
        while(lower < builder.length()) {
            int eqAt = builder.indexOf("=", lower);
            if(eqAt == -1) {
                eqAt = builder.length();
            }
            int ampAt = builder.indexOf("&", eqAt);
            if(ampAt == -1) {
                ampAt = builder.length();
            }
            
            final String key = URLDecoder.decode(builder.substring(lower, eqAt), charset.toString());
            final String value = eqAt == ampAt ? null : URLDecoder.decode(builder.substring(eqAt+1, ampAt), charset.toString());
            List<String> values = ret.get(key);
            if(values == null) {
                values = new ArrayList<>();
                ret.put(key, values);
            }
            
            if(ampAt - eqAt > 1) {
                values.add(value);
            }
            
            lower = ampAt + 1;
        }
        
        return ret;
    }

    public static List<?> checkValue(final Object v) {
        if(v instanceof List) {
            List list = (List) v;
            if(list.size() > 0) {
                return list;
            }
            else {
                return Collections.singletonList("");
            }
        }
        else if(v != null) {
            return Collections.singletonList(v);
        }
        else {
            return Collections.singletonList("");
        }
    }
    
    public static String encode(final Map<?,?> map, final Charset charset) {
        try {
            tlBuffers.get().clear();
            final StringBuilder builder = tlBuffers.get().builder;
            for(Map.Entry<?,?> entry : map.entrySet()) {
                for(Object value : checkValue(entry.getValue())) {
                    builder.append(URLEncoder.encode(entry.getKey().toString(), charset.toString())).append("=");
                    builder.append(URLEncoder.encode(value.toString(), charset.toString())).append("&");
                }
            }
            
            return builder.substring(0, builder.length() -1);
        }
        catch(UnsupportedEncodingException e) {
            throw new TransportingException("Error in encoding form", e);
        }
    }
}