/* * 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.jena.atlas.web; import java.util.* ; import org.apache.jena.atlas.logging.Log ; public class AcceptList { private List<MediaRange> ranges ; /** * Create an empty list of accept items from the give strings. * @param acceptStrings */ private AcceptList() { ranges = new ArrayList<>() ; } /** * Create a list of accept items from the give strings. * @param mediaRanges */ public AcceptList(List<MediaRange> mediaRanges) { ranges = new ArrayList<>(mediaRanges) ; } /** * Create a list of accept items from the give MediaTypes. * @param acceptItems */ public AcceptList(MediaRange...acceptItems) { ranges = Arrays.asList(acceptItems) ; } /** * Create a list of accept items from the give MediaTypes. * @param acceptItems */ public static AcceptList create(MediaType... acceptItems) { AcceptList accepList = new AcceptList(); for ( MediaType mtype : acceptItems ) accepList.ranges.add(new MediaRange(mtype)); return accepList; } /** * Create a list of accept items from strings. * @param acceptStrings */ public static AcceptList create(String... acceptStrings) { AcceptList accepList = new AcceptList(); for ( String acceptString : acceptStrings ) { accepList.ranges.add(new MediaRange(acceptString)); } return accepList; } /** * Parse an HTTP Accept (or etc) header string. * @param headerString */ public AcceptList(String headerString) { try { ranges = stringToAcceptList(headerString); } catch (Exception ex) { ex.printStackTrace(System.err); Log.warn(this, "Unrecognized accept string (ignored): " + headerString); ranges = new ArrayList<>(); } } private /* public */ boolean accepts(MediaRange aItem) { return match(aItem) != null; } public List<MediaRange> entries() { return Collections.unmodifiableList(ranges); } private final static MediaRangeCompare comparator = new MediaRangeCompare() ; /** Find and return a match for a specific MediaType. * Returns the Accept header entry best matched or null. */ public MediaRange match(MediaType offer) { // "this" is the client proposal list (Accept header) // aItem is one item of the server offer list - a concrete media type (no "*") // Search all, find best by specifivity, "q"(quality), and then first occurring if otherwise equal. MediaRange choice = null ; double weight = -1 ; int exact = -1 ; for ( MediaRange acceptItem : ranges ) { if ( acceptItem.accepts(offer) ) { boolean newChoice = false; if ( choice == null ) // First possibility. newChoice = true ; else if ( weight < acceptItem.get_q() ) // New possibility has greater weight newChoice = true ; else if ( weight == acceptItem.get_q() && exact < acceptItem.subweight() ) // New possibility has same weight but better exactness. newChoice = true ; if ( newChoice ) { choice = acceptItem; weight = acceptItem.get_q(); exact = acceptItem.subweight(); continue; } //if ( weight == acceptItem.get_q() && !exact && } } if ( choice == null ) return null ; return choice ; } /** Find the best thing in offer list 8sever side) matching the proposal (client, Accept header). * "best" means highest q value, with left most being better for same q. * * @param proposalList Client list of possibilities * @param offerList Server list of possibilities * @return MediaType */ static public MediaType match(AcceptList proposalList, AcceptList offerList) { MediaRange cause = null; MediaRange choice = null ; // From the proposalList double weight = -1 ; int exactness = -1 ; for ( MediaRange offer : offerList.entries() ) { MediaRange m = proposalList.match(offer); if ( m == null ) continue; boolean newChoice = false; if ( choice == null ) newChoice = true ; else if ( weight < m.get_q() ) newChoice = true ; else if ( weight == m.get_q() && ( exactness < m.subweight() ) ) newChoice = true ; if ( newChoice ) { choice = offer ; weight = m.get_q() ; exactness = m.subweight() ; } } if ( choice == null ) return null ; return new MediaType(choice); } public MediaRange first() { MediaRange choice = null; for ( MediaRange acceptItem : ranges ) { if ( choice != null && choice.get_q() >= acceptItem.get_q() ) continue; choice = acceptItem; } return choice; } @Override public String toString() { return ranges.toString() ; } private static List<MediaRange> stringToAcceptList(String s) { List<MediaRange> ranges = new ArrayList<>(); if ( s == null ) return ranges; String[] x = s.split(","); for ( String aX : x ) { if ( aX.equals("") ) { continue; } MediaRange mType = new MediaRange(aX); ranges.add(mType); } return ranges; } private static class MediaRangeCompare implements Comparator<MediaRange> { @Override public int compare(MediaRange mType1, MediaRange mType2) { int r = Double.compare(mType1.get_q(), mType2.get_q()) ; if ( r == 0 ) r = subCompare(mType1.getType(), mType2.getType()) ; if ( r == 0 ) r = subCompare(mType1.getSubType(), mType2.getSubType()) ; // if ( r == 0 ) { // // This reverses the input order so that the rightmost elements is the // // greatest and hence is the first mentioned in the accept range. // // if ( mType1.posn < mType2.posn ) // r = +1 ; // if ( mType1.posn > mType2.posn ) // r = -1 ; // } // The most significant sorts to the first in a list. r = -r ; return r ; } public int subCompare(String a, String b) { if ( a == null ) return 1; if ( b == null ) return -1; if ( a.equals("*") && b.equals("*") ) return 0; if ( a.equals("*") ) return -1; if ( b.equals("*") ) return 1; return 0; } } }