Project

General

Profile

« Previous | Next » 

Revision 6df5928c

Added by Andreas Müller over 1 year ago

cleanup

View differences:

cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/media/MediaUtils.java
1
package eu.etaxonomy.cdm.model.media;
2

  
3
import java.awt.Dimension;
4
import java.util.ArrayList;
5
import java.util.HashMap;
6
import java.util.LinkedHashMap;
7
import java.util.LinkedHashSet;
8
import java.util.List;
9
import java.util.Map;
10
import java.util.Set;
11
import java.util.SortedMap;
12
import java.util.TreeMap;
13
import java.util.regex.Pattern;
14
import java.util.stream.Collectors;
15

  
16
import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;
17

  
18
import eu.etaxonomy.cdm.model.common.CdmBase;
19

  
20
public class MediaUtils {
21

  
22
    private static final Logger logger = LogManager.getLogger(MediaUtils.class);
23

  
24

  
25
    public static MediaRepresentation findBestMatchingRepresentation(Media media,
26
            Class<? extends MediaRepresentationPart> representationPartType, Integer size, Integer height,
27
            Integer widthOrDuration, String[] mimeTypes, MissingValueStrategy missingValStrategy){
28

  
29
        // find best matching representations of each media
30
        Set<MediaRepresentation> representations = media.getRepresentations();
31
        return findBestMatchingRepresentation(representations, representationPartType, size, height, widthOrDuration,
32
                mimeTypes, missingValStrategy);
33
    }
34

  
35
    public static MediaRepresentation findBestMatchingRepresentation(
36
            Set<MediaRepresentation> representations, Class<? extends MediaRepresentationPart> representationPartType, Integer size,
37
            Integer height, Integer widthOrDuration, String[] mimeTypes,
38
            MissingValueStrategy missingValStrategy) {
39

  
40
        SortedMap<Long, MediaRepresentation> prefRepresentations
41
                = filterAndOrderMediaRepresentations(representations, representationPartType, mimeTypes,
42
                        size, widthOrDuration, height, missingValStrategy);
43
        if(prefRepresentations.size() > 0){
44
            MediaRepresentation prefOne = prefRepresentations.get(prefRepresentations.firstKey());
45
            return prefOne;
46
        }
47
        return null;
48
    }
49

  
50
    /**
51
     * Return the first {@link MediaRepresentationPart} found for the given {@link Media}
52
     * or <code>null</code> otherwise.
53
     * @param media the media which is searched for the first part
54
     * @return the first part found or <code>null</code>
55
     */
56
    public static MediaRepresentationPart getFirstMediaRepresentationPart(Media media){
57
        if(media==null){
58
            return null;
59
        }
60
        MediaRepresentationPart mediaRepresentationPart = null;
61
        Set<MediaRepresentation> representations = media.getRepresentations();
62
        if(representations!=null && representations.size()>0){
63
            MediaRepresentation mediaRepresentation = representations.iterator().next();
64
            List<MediaRepresentationPart> parts = mediaRepresentation.getParts();
65
            if(parts!=null && parts.size()>0){
66
                mediaRepresentationPart = parts.iterator().next();
67
            }
68
        }
69
        return mediaRepresentationPart;
70
    }
71

  
72
    /**
73
     * Creates one single {@link MediaRepresentationPart} for the given {@link Media}
74
     * if it does not already exists. Otherwise the first part found is returned.<br>
75
     * @param media the media for which the representation part should be created
76
     * @return the first or newly created representation part
77
     */
78
    public static MediaRepresentationPart initFirstMediaRepresentationPart(Media media, boolean isImage) {
79
        MediaRepresentationPart mediaRepresentationPart = getFirstMediaRepresentationPart(media);
80
        if(mediaRepresentationPart==null){
81
            Set<MediaRepresentation> representations = media.getRepresentations();
82
            if(representations!=null && representations.size()>0){
83
                MediaRepresentation mediaRepresentation = representations.iterator().next();
84
                if(isImage){
85
                    mediaRepresentationPart = ImageFile.NewInstance(null, null);
86
                }
87
                else{
88
                    mediaRepresentationPart = MediaRepresentationPart.NewInstance(null, null);
89
                }
90
                mediaRepresentation.addRepresentationPart(mediaRepresentationPart);
91
            }
92
            else{
93
                if(isImage){
94
                    mediaRepresentationPart = ImageFile.NewInstance(null, null);
95
                }
96
                else{
97
                    mediaRepresentationPart = MediaRepresentationPart.NewInstance(null, null);
98
                }
99

  
100
                MediaRepresentation mediaRepresentation = MediaRepresentation.NewInstance();
101
                mediaRepresentation.addRepresentationPart(mediaRepresentationPart);
102
                media.addRepresentation(mediaRepresentation);
103
            }
104
        }
105
        return mediaRepresentationPart;
106
    }
107

  
108
    /**
109
     * Filters the given List of Media by the supplied filter parameters <code>representationPartType</code>,
110
     * <code>mimeTypes</code>, <code>widthOrDuration</code>, <code>height</code>, <code>size</code>.
111
     * Only best matching MediaRepresentation remains attached to the Media entities.
112
     * A Media entity may be completely omitted in the resulting list if  {@link #filterAndOrderMediaRepresentations(Set, Class, String[], Integer, Integer, Integer)}
113
     * is not returning any matching representation. This can be the case if a <code>representationPartType</code> is supplied.
114
     *
115
     * @param mediaList
116
     * @param representationPartType any subclass of {@link MediaRepresentationPart}
117
     * @param mimeTypes
118
     * @param widthOrDuration
119
     * @param height
120
     * @param size
121
     * @return
122
     */
123
    public static Map<Media, MediaRepresentation> findPreferredMedia(List<Media> mediaList,
124
            Class<? extends MediaRepresentationPart> representationPartType, String[] mimeTypes, Integer widthOrDuration,
125
            Integer height, Integer size, MissingValueStrategy missingValStrat) {
126

  
127
        if(mimeTypes != null) {
128
            for(int i=0; i<mimeTypes.length; i++){
129
                mimeTypes[i] = mimeTypes[i].replace(':', '/');
130
            }
131
        }
132

  
133
        Map<Media, MediaRepresentation> returnMediaList;
134
        if(mediaList != null){
135
            returnMediaList = new LinkedHashMap<>(mediaList.size());
136
            for(Media media : mediaList){
137

  
138
                Set<MediaRepresentation> candidateRepresentations = new LinkedHashSet<>();
139
                candidateRepresentations.addAll(media.getRepresentations());
140

  
141
                SortedMap<Long, MediaRepresentation> prefRepresentations
142
                    = filterAndOrderMediaRepresentations(candidateRepresentations, representationPartType,
143
                            mimeTypes, size, widthOrDuration, height, missingValStrat);
144

  
145
                if(prefRepresentations.size() > 0){
146
                    // Media.representations is a set
147
                    // so it cannot retain the sorting which has been found by filterAndOrderMediaRepresentations()
148
                    // thus we take first one and remove all other representations
149
                    returnMediaList.put(media, prefRepresentations.get(prefRepresentations.firstKey()));
150
                }
151

  
152
            }
153
        }
154
        else{
155
            returnMediaList = new HashMap<>();
156
        }
157
        return returnMediaList;
158
    }
159

  
160
    /**
161
     * @see also cdm-dataportal: cdm-api.module#cdm_preferred_media_representations()
162
     *
163
     * @param mediaRepresentations
164
     * @param representationPartType
165
     * @param mimeTypeRegexes
166
     * @param size
167
     *  Applies to all {@link MediaRepresentationPart}s (value = <code>null</code> means ignore, for maximum size use {@link Integer#MAX_VALUE})
168
     * @param widthOrDuration
169
     *   Applied to {@link ImageFile#getWidth()}, or {@link {@link MovieFile#getDuration()},
170
     *   or {@link {@link AudioFile#getDuration()} (value = <code>null</code> means ignore,
171
     *   for maximum use {@link Integer#MAX_VALUE})
172
     * @param height
173
     *   The height is only applied to {@link ImageFile}s (value = <code>null</code> means ignore,
174
     *   for maximum height use {@link Integer#MAX_VALUE})
175
     * @return
176
     */
177
    public static SortedMap<Long, MediaRepresentation> filterAndOrderMediaRepresentations(
178
            Set<MediaRepresentation> mediaRepresentations,
179
            Class<? extends MediaRepresentationPart> representationPartType, String[] mimeTypeRegexes,
180
            Integer size, Integer widthOrDuration, Integer height,
181
            MissingValueStrategy missingValStrat) {
182

  
183
        SortedMap<Long, MediaRepresentation> prefRepr = new TreeMap<>();
184

  
185
        Dimension preferredImageDimensions = dimensionsFilter(widthOrDuration, height, null);
186
        long preferredExpansion = expanse(preferredImageDimensions);
187
        logger.debug("preferredExpansion: " + preferredExpansion);
188

  
189
        mimeTypeRegexes = (mimeTypeRegexes == null ? new String[]{".*"} : mimeTypeRegexes);
190

  
191
        for (String mimeTypeRegex : mimeTypeRegexes) {
192
            // getRepresentationByMimeType
193
            Pattern mimeTypePattern = Pattern.compile(mimeTypeRegex);
194
            int representationCnt = 0;
195
            for (MediaRepresentation representation : mediaRepresentations) {
196

  
197
                List<MediaRepresentationPart> matchingParts = new ArrayList<>();
198

  
199

  
200
                // check MIME type
201
                boolean isMimeTypeMatch = representation.getMimeType() == null
202
                        || mimeTypePattern.matcher(representation.getMimeType()).matches();
203
                if(logger.isDebugEnabled()){
204
                    logger.debug("isMimeTypeMatch: " + Boolean.valueOf(isMimeTypeMatch).toString());
205
                }
206

  
207
                long dimensionsDeltaAllParts = 0;
208

  
209
                //first the size is used for comparison
210
                for (MediaRepresentationPart part : representation.getParts()) {
211

  
212
                    // check representationPartType
213
                    boolean isRepresentationPartTypeMatch = representationPartType == null
214
                            || part.getClass().isAssignableFrom(representationPartType);
215
                    if(logger.isDebugEnabled()){
216
                        logger.debug("isRepresentationPartTypeMatch: " + Boolean.valueOf(isRepresentationPartTypeMatch).toString());
217
                    }
218

  
219
                    if ( !(isRepresentationPartTypeMatch && isMimeTypeMatch) ) {
220
                        continue;
221
                    }
222

  
223
                    if(logger.isDebugEnabled()){
224
                        logger.debug(part + " matches");
225
                    }
226
                    matchingParts.add(part);
227

  
228
                    Integer sizeOfPart = part.getSize();
229
                    if(isUndefined(sizeOfPart)){
230
                        sizeOfPart = missingValStrat.applyTo(sizeOfPart);
231
                    }
232
                    if (size != null && sizeOfPart != null){
233
                        int distance = sizeOfPart - size;
234
                        if (distance < 0) {
235
                            distance *= -1;
236
                        }
237
                        dimensionsDeltaAllParts += distance;
238

  
239
                    }
240

  
241
                    //if height and width/duration is defined, add this information, too
242
                    if (preferredImageDimensions != null || widthOrDuration != null){
243
                        long expansionDelta = 0;
244
                        if (part.isInstanceOf(ImageFile.class)) {
245
                            if (preferredImageDimensions != null){
246
                                ImageFile image = CdmBase.deproxy(part, ImageFile.class);
247
                                Dimension imageDimension = dimensionsFilter(image.getWidth(), image.getHeight(), missingValStrat);
248
                                if (imageDimension != null){
249
                                    expansionDelta = Math.abs(expanse(imageDimension) - preferredExpansion);
250
                                }
251
                                if(logger.isDebugEnabled()){
252
                                    if(logger.isDebugEnabled()){
253
                                        logger.debug("part [" + part.getUri() + "; " + imageDimension + "] : preferredImageDimensions= " + preferredImageDimensions + ", size= "  + size+ " >>" + expansionDelta );
254
                                    }
255
                                }
256
                            }
257
                        }
258
                        else if (part.isInstanceOf(MovieFile.class)){
259
                             MovieFile movie = CdmBase.deproxy(part, MovieFile.class);
260
                             Integer durationOfMovie = movie.getDuration();
261
                             if(isUndefined(durationOfMovie)){
262
                                 durationOfMovie = null; // convert potential 0 to null!
263
                             }
264
                             durationOfMovie = missingValStrat.applyTo(durationOfMovie);
265
                             if(widthOrDuration != null){
266
                                expansionDelta = durationOfMovie - widthOrDuration;
267
                            }
268
                             if(logger.isDebugEnabled()){
269
                                 logger.debug("part MovieFile[" + part.getUri() + "; duration=" + movie.getDuration() + "-> " + durationOfMovie + "] : preferrdDuration= " + widthOrDuration + ", size= "  + size+ " >>" + expansionDelta );
270
                             }
271
                        } else if (part.isInstanceOf(AudioFile.class)){
272
                            AudioFile audio = CdmBase.deproxy(part, AudioFile.class);
273
                            Integer durationOfAudio = audio.getDuration();
274
                            if(isUndefined(durationOfAudio)){
275
                                durationOfAudio = null;  // convert potential 0 to null!
276
                            }
277
                            durationOfAudio = missingValStrat.applyTo(durationOfAudio);
278
                            if(widthOrDuration != null) {
279
                                expansionDelta = durationOfAudio - widthOrDuration;
280
                            }
281
                            if(logger.isDebugEnabled()){
282
                                logger.debug("part AudioFile[" + part.getUri() + "; duration=" +  audio.getDuration() + "-> " + durationOfAudio + "] : preferrdDuration= " + widthOrDuration + ", size= "  + size + " >>" + expansionDelta );
283
                            }
284
                        }
285
                        // the expansionDelta is summed up since the parts together for the whole
286
                        // which is bigger than only a part. By simply summing up images splitted
287
                        // into parts have too much weight compared to the single image but since
288
                        // parts are not used at all this is currently not a problem
289
                        dimensionsDeltaAllParts += expansionDelta;
290

  
291
                    }
292
                } // loop parts
293
                logger.debug("matchingParts.size():" + matchingParts.size());
294
                if(matchingParts.size() > 0 ){
295
                    representation.getParts().clear();
296
                    representation.getParts().addAll(matchingParts);
297
                    prefRepr.put((dimensionsDeltaAllParts + representationCnt++), representation);
298
                }
299
            } // loop representations
300
        } // loop mime types
301
        if(logger.isDebugEnabled()){
302
            String text =  prefRepr.keySet().stream()
303
            .map(key -> key + ": " + prefRepr.get(key).getParts().get(0).getUri().toString())
304
            .collect(Collectors.joining(", ", "{", "}"));
305
            logger.debug("resulting representations: " + text);
306
        }
307

  
308

  
309
        return prefRepr;
310

  
311
    }
312

  
313
    /**
314
     * @param imageDimension
315
     * @return
316
     */
317
    static long expanse(Dimension imageDimension) {
318
        if(imageDimension != null){
319
            return (long)imageDimension.height * (long)imageDimension.width;
320
        } else {
321
            return -1;
322
        }
323
    }
324

  
325
    /**
326
     * @param widthOrDuration
327
     * @param height
328
     * @param mvs Will be applied when both, width and height, are <code>null</code>
329
     */
330
    static Dimension dimensionsFilter(Integer width, Integer height, MissingValueStrategy mvs) {
331
        Dimension imageDimensions = null;
332
        if(!isUndefined(height) || !isUndefined(width)){
333
            imageDimensions = new Dimension();
334
            if (!isUndefined(height) && isUndefined(width)){
335
                imageDimensions.setSize(1, height);
336
            } else if(isUndefined(height) && !isUndefined(width)) {
337
                imageDimensions.setSize(width, 1); // --> height will be respected and width is ignored
338
            } else {
339
                imageDimensions.setSize(width, height);
340
            }
341
        } else {
342
            // both, width and height, are undefined
343

  
344
            if(mvs != null){
345
                // set both values to null so that the MissingValueStrategy can be applied
346
                // the MissingValueStrategy only get effective when the supplied value  is NULL
347
                width = null;
348
                height = null;
349
                imageDimensions = new Dimension(mvs.applyTo(width), mvs.applyTo(height));
350
            }
351
        }
352
        return imageDimensions;
353
    }
354

  
355
    static private boolean isUndefined(Integer val) {
356
        return val == null || val == 0;
357
    }
358

  
359
    /**
360
     * Strategies for replacing <code>null</code> values with a numeric value.
361
     *
362
     * @author a.kohlbecker
363
     */
364
    public enum MissingValueStrategy {
365
        /**
366
         * replace <code>null</code> by {@link Integer#MAX_VALUE}
367
         */
368
        MAX(Integer.MAX_VALUE),
369
        /**
370
         * replace <code>null</code> by <code>0</code>
371
         */
372
        ZERO(0);
373

  
374
        private Integer defaultValue;
375

  
376
        MissingValueStrategy(Integer defaultValue){
377
            this.defaultValue = defaultValue;
378
        }
379

  
380
        public Integer applyTo(Integer val){
381
            if(val == null){
382
                return defaultValue;
383
            } else {
384
                return val;
385
            }
386
        }
387
    }
388
  }
1
package eu.etaxonomy.cdm.model.media;
2

  
3
import java.awt.Dimension;
4
import java.util.ArrayList;
5
import java.util.HashMap;
6
import java.util.LinkedHashMap;
7
import java.util.LinkedHashSet;
8
import java.util.List;
9
import java.util.Map;
10
import java.util.Set;
11
import java.util.SortedMap;
12
import java.util.TreeMap;
13
import java.util.regex.Pattern;
14
import java.util.stream.Collectors;
15

  
16
import org.apache.logging.log4j.LogManager;
17
import org.apache.logging.log4j.Logger;
18

  
19
import eu.etaxonomy.cdm.model.common.CdmBase;
20

  
21
public class MediaUtils {
22

  
23
    private static final Logger logger = LogManager.getLogger();
24

  
25

  
26
    public static MediaRepresentation findBestMatchingRepresentation(Media media,
27
            Class<? extends MediaRepresentationPart> representationPartType, Integer size, Integer height,
28
            Integer widthOrDuration, String[] mimeTypes, MissingValueStrategy missingValStrategy){
29

  
30
        // find best matching representations of each media
31
        Set<MediaRepresentation> representations = media.getRepresentations();
32
        return findBestMatchingRepresentation(representations, representationPartType, size, height, widthOrDuration,
33
                mimeTypes, missingValStrategy);
34
    }
35

  
36
    public static MediaRepresentation findBestMatchingRepresentation(
37
            Set<MediaRepresentation> representations, Class<? extends MediaRepresentationPart> representationPartType, Integer size,
38
            Integer height, Integer widthOrDuration, String[] mimeTypes,
39
            MissingValueStrategy missingValStrategy) {
40

  
41
        SortedMap<Long, MediaRepresentation> prefRepresentations
42
                = filterAndOrderMediaRepresentations(representations, representationPartType, mimeTypes,
43
                        size, widthOrDuration, height, missingValStrategy);
44
        if(prefRepresentations.size() > 0){
45
            MediaRepresentation prefOne = prefRepresentations.get(prefRepresentations.firstKey());
46
            return prefOne;
47
        }
48
        return null;
49
    }
50

  
51
    /**
52
     * Return the first {@link MediaRepresentationPart} found for the given {@link Media}
53
     * or <code>null</code> otherwise.
54
     * @param media the media which is searched for the first part
55
     * @return the first part found or <code>null</code>
56
     */
57
    public static MediaRepresentationPart getFirstMediaRepresentationPart(Media media){
58
        if(media==null){
59
            return null;
60
        }
61
        MediaRepresentationPart mediaRepresentationPart = null;
62
        Set<MediaRepresentation> representations = media.getRepresentations();
63
        if(representations!=null && representations.size()>0){
64
            MediaRepresentation mediaRepresentation = representations.iterator().next();
65
            List<MediaRepresentationPart> parts = mediaRepresentation.getParts();
66
            if(parts!=null && parts.size()>0){
67
                mediaRepresentationPart = parts.iterator().next();
68
            }
69
        }
70
        return mediaRepresentationPart;
71
    }
72

  
73
    /**
74
     * Creates one single {@link MediaRepresentationPart} for the given {@link Media}
75
     * if it does not already exists. Otherwise the first part found is returned.<br>
76
     * @param media the media for which the representation part should be created
77
     * @return the first or newly created representation part
78
     */
79
    public static MediaRepresentationPart initFirstMediaRepresentationPart(Media media, boolean isImage) {
80
        MediaRepresentationPart mediaRepresentationPart = getFirstMediaRepresentationPart(media);
81
        if(mediaRepresentationPart==null){
82
            Set<MediaRepresentation> representations = media.getRepresentations();
83
            if(representations!=null && representations.size()>0){
84
                MediaRepresentation mediaRepresentation = representations.iterator().next();
85
                if(isImage){
86
                    mediaRepresentationPart = ImageFile.NewInstance(null, null);
87
                }
88
                else{
89
                    mediaRepresentationPart = MediaRepresentationPart.NewInstance(null, null);
90
                }
91
                mediaRepresentation.addRepresentationPart(mediaRepresentationPart);
92
            }
93
            else{
94
                if(isImage){
95
                    mediaRepresentationPart = ImageFile.NewInstance(null, null);
96
                }
97
                else{
98
                    mediaRepresentationPart = MediaRepresentationPart.NewInstance(null, null);
99
                }
100

  
101
                MediaRepresentation mediaRepresentation = MediaRepresentation.NewInstance();
102
                mediaRepresentation.addRepresentationPart(mediaRepresentationPart);
103
                media.addRepresentation(mediaRepresentation);
104
            }
105
        }
106
        return mediaRepresentationPart;
107
    }
108

  
109
    /**
110
     * Filters the given List of Media by the supplied filter parameters <code>representationPartType</code>,
111
     * <code>mimeTypes</code>, <code>widthOrDuration</code>, <code>height</code>, <code>size</code>.
112
     * Only best matching MediaRepresentation remains attached to the Media entities.
113
     * A Media entity may be completely omitted in the resulting list if  {@link #filterAndOrderMediaRepresentations(Set, Class, String[], Integer, Integer, Integer)}
114
     * is not returning any matching representation. This can be the case if a <code>representationPartType</code> is supplied.
115
     *
116
     * @param mediaList
117
     * @param representationPartType any subclass of {@link MediaRepresentationPart}
118
     * @param mimeTypes
119
     * @param widthOrDuration
120
     * @param height
121
     * @param size
122
     * @return
123
     */
124
    public static Map<Media, MediaRepresentation> findPreferredMedia(List<Media> mediaList,
125
            Class<? extends MediaRepresentationPart> representationPartType, String[] mimeTypes, Integer widthOrDuration,
126
            Integer height, Integer size, MissingValueStrategy missingValStrat) {
127

  
128
        if(mimeTypes != null) {
129
            for(int i=0; i<mimeTypes.length; i++){
130
                mimeTypes[i] = mimeTypes[i].replace(':', '/');
131
            }
132
        }
133

  
134
        Map<Media, MediaRepresentation> returnMediaList;
135
        if(mediaList != null){
136
            returnMediaList = new LinkedHashMap<>(mediaList.size());
137
            for(Media media : mediaList){
138

  
139
                Set<MediaRepresentation> candidateRepresentations = new LinkedHashSet<>();
140
                candidateRepresentations.addAll(media.getRepresentations());
141

  
142
                SortedMap<Long, MediaRepresentation> prefRepresentations
143
                    = filterAndOrderMediaRepresentations(candidateRepresentations, representationPartType,
144
                            mimeTypes, size, widthOrDuration, height, missingValStrat);
145

  
146
                if(prefRepresentations.size() > 0){
147
                    // Media.representations is a set
148
                    // so it cannot retain the sorting which has been found by filterAndOrderMediaRepresentations()
149
                    // thus we take first one and remove all other representations
150
                    returnMediaList.put(media, prefRepresentations.get(prefRepresentations.firstKey()));
151
                }
152

  
153
            }
154
        }
155
        else{
156
            returnMediaList = new HashMap<>();
157
        }
158
        return returnMediaList;
159
    }
160

  
161
    /**
162
     * @see also cdm-dataportal: cdm-api.module#cdm_preferred_media_representations()
163
     *
164
     * @param mediaRepresentations
165
     * @param representationPartType
166
     * @param mimeTypeRegexes
167
     * @param size
168
     *  Applies to all {@link MediaRepresentationPart}s (value = <code>null</code> means ignore, for maximum size use {@link Integer#MAX_VALUE})
169
     * @param widthOrDuration
170
     *   Applied to {@link ImageFile#getWidth()}, or {@link {@link MovieFile#getDuration()},
171
     *   or {@link {@link AudioFile#getDuration()} (value = <code>null</code> means ignore,
172
     *   for maximum use {@link Integer#MAX_VALUE})
173
     * @param height
174
     *   The height is only applied to {@link ImageFile}s (value = <code>null</code> means ignore,
175
     *   for maximum height use {@link Integer#MAX_VALUE})
176
     * @return
177
     */
178
    public static SortedMap<Long, MediaRepresentation> filterAndOrderMediaRepresentations(
179
            Set<MediaRepresentation> mediaRepresentations,
180
            Class<? extends MediaRepresentationPart> representationPartType, String[] mimeTypeRegexes,
181
            Integer size, Integer widthOrDuration, Integer height,
182
            MissingValueStrategy missingValStrat) {
183

  
184
        SortedMap<Long, MediaRepresentation> prefRepr = new TreeMap<>();
185

  
186
        Dimension preferredImageDimensions = dimensionsFilter(widthOrDuration, height, null);
187
        long preferredExpansion = expanse(preferredImageDimensions);
188
        logger.debug("preferredExpansion: " + preferredExpansion);
189

  
190
        mimeTypeRegexes = (mimeTypeRegexes == null ? new String[]{".*"} : mimeTypeRegexes);
191

  
192
        for (String mimeTypeRegex : mimeTypeRegexes) {
193
            // getRepresentationByMimeType
194
            Pattern mimeTypePattern = Pattern.compile(mimeTypeRegex);
195
            int representationCnt = 0;
196
            for (MediaRepresentation representation : mediaRepresentations) {
197

  
198
                List<MediaRepresentationPart> matchingParts = new ArrayList<>();
199

  
200

  
201
                // check MIME type
202
                boolean isMimeTypeMatch = representation.getMimeType() == null
203
                        || mimeTypePattern.matcher(representation.getMimeType()).matches();
204
                if(logger.isDebugEnabled()){
205
                    logger.debug("isMimeTypeMatch: " + Boolean.valueOf(isMimeTypeMatch).toString());
206
                }
207

  
208
                long dimensionsDeltaAllParts = 0;
209

  
210
                //first the size is used for comparison
211
                for (MediaRepresentationPart part : representation.getParts()) {
212

  
213
                    // check representationPartType
214
                    boolean isRepresentationPartTypeMatch = representationPartType == null
215
                            || part.getClass().isAssignableFrom(representationPartType);
216
                    if(logger.isDebugEnabled()){
217
                        logger.debug("isRepresentationPartTypeMatch: " + Boolean.valueOf(isRepresentationPartTypeMatch).toString());
218
                    }
219

  
220
                    if ( !(isRepresentationPartTypeMatch && isMimeTypeMatch) ) {
221
                        continue;
222
                    }
223

  
224
                    if(logger.isDebugEnabled()){
225
                        logger.debug(part + " matches");
226
                    }
227
                    matchingParts.add(part);
228

  
229
                    Integer sizeOfPart = part.getSize();
230
                    if(isUndefined(sizeOfPart)){
231
                        sizeOfPart = missingValStrat.applyTo(sizeOfPart);
232
                    }
233
                    if (size != null && sizeOfPart != null){
234
                        int distance = sizeOfPart - size;
235
                        if (distance < 0) {
236
                            distance *= -1;
237
                        }
238
                        dimensionsDeltaAllParts += distance;
239

  
240
                    }
241

  
242
                    //if height and width/duration is defined, add this information, too
243
                    if (preferredImageDimensions != null || widthOrDuration != null){
244
                        long expansionDelta = 0;
245
                        if (part.isInstanceOf(ImageFile.class)) {
246
                            if (preferredImageDimensions != null){
247
                                ImageFile image = CdmBase.deproxy(part, ImageFile.class);
248
                                Dimension imageDimension = dimensionsFilter(image.getWidth(), image.getHeight(), missingValStrat);
249
                                if (imageDimension != null){
250
                                    expansionDelta = Math.abs(expanse(imageDimension) - preferredExpansion);
251
                                }
252
                                if(logger.isDebugEnabled()){
253
                                    if(logger.isDebugEnabled()){
254
                                        logger.debug("part [" + part.getUri() + "; " + imageDimension + "] : preferredImageDimensions= " + preferredImageDimensions + ", size= "  + size+ " >>" + expansionDelta );
255
                                    }
256
                                }
257
                            }
258
                        }
259
                        else if (part.isInstanceOf(MovieFile.class)){
260
                             MovieFile movie = CdmBase.deproxy(part, MovieFile.class);
261
                             Integer durationOfMovie = movie.getDuration();
262
                             if(isUndefined(durationOfMovie)){
263
                                 durationOfMovie = null; // convert potential 0 to null!
264
                             }
265
                             durationOfMovie = missingValStrat.applyTo(durationOfMovie);
266
                             if(widthOrDuration != null){
267
                                expansionDelta = durationOfMovie - widthOrDuration;
268
                            }
269
                             if(logger.isDebugEnabled()){
270
                                 logger.debug("part MovieFile[" + part.getUri() + "; duration=" + movie.getDuration() + "-> " + durationOfMovie + "] : preferrdDuration= " + widthOrDuration + ", size= "  + size+ " >>" + expansionDelta );
271
                             }
272
                        } else if (part.isInstanceOf(AudioFile.class)){
273
                            AudioFile audio = CdmBase.deproxy(part, AudioFile.class);
274
                            Integer durationOfAudio = audio.getDuration();
275
                            if(isUndefined(durationOfAudio)){
276
                                durationOfAudio = null;  // convert potential 0 to null!
277
                            }
278
                            durationOfAudio = missingValStrat.applyTo(durationOfAudio);
279
                            if(widthOrDuration != null) {
280
                                expansionDelta = durationOfAudio - widthOrDuration;
281
                            }
282
                            if(logger.isDebugEnabled()){
283
                                logger.debug("part AudioFile[" + part.getUri() + "; duration=" +  audio.getDuration() + "-> " + durationOfAudio + "] : preferrdDuration= " + widthOrDuration + ", size= "  + size + " >>" + expansionDelta );
284
                            }
285
                        }
286
                        // the expansionDelta is summed up since the parts together for the whole
287
                        // which is bigger than only a part. By simply summing up images splitted
288
                        // into parts have too much weight compared to the single image but since
289
                        // parts are not used at all this is currently not a problem
290
                        dimensionsDeltaAllParts += expansionDelta;
291

  
292
                    }
293
                } // loop parts
294
                logger.debug("matchingParts.size():" + matchingParts.size());
295
                if(matchingParts.size() > 0 ){
296
                    representation.getParts().clear();
297
                    representation.getParts().addAll(matchingParts);
298
                    prefRepr.put((dimensionsDeltaAllParts + representationCnt++), representation);
299
                }
300
            } // loop representations
301
        } // loop mime types
302
        if(logger.isDebugEnabled()){
303
            String text =  prefRepr.keySet().stream()
304
            .map(key -> key + ": " + prefRepr.get(key).getParts().get(0).getUri().toString())
305
            .collect(Collectors.joining(", ", "{", "}"));
306
            logger.debug("resulting representations: " + text);
307
        }
308

  
309
        return prefRepr;
310
    }
311

  
312
    static long expanse(Dimension imageDimension) {
313
        if(imageDimension != null){
314
            return (long)imageDimension.height * (long)imageDimension.width;
315
        } else {
316
            return -1;
317
        }
318
    }
319

  
320
    /**
321
     * @param widthOrDuration
322
     * @param height
323
     * @param mvs Will be applied when both, width and height, are <code>null</code>
324
     */
325
    static Dimension dimensionsFilter(Integer width, Integer height, MissingValueStrategy mvs) {
326
        Dimension imageDimensions = null;
327
        if(!isUndefined(height) || !isUndefined(width)){
328
            imageDimensions = new Dimension();
329
            if (!isUndefined(height) && isUndefined(width)){
330
                imageDimensions.setSize(1, height);
331
            } else if(isUndefined(height) && !isUndefined(width)) {
332
                imageDimensions.setSize(width, 1); // --> height will be respected and width is ignored
333
            } else {
334
                imageDimensions.setSize(width, height);
335
            }
336
        } else {
337
            // both, width and height, are undefined
338

  
339
            if(mvs != null){
340
                // set both values to null so that the MissingValueStrategy can be applied
341
                // the MissingValueStrategy only get effective when the supplied value  is NULL
342
                width = null;
343
                height = null;
344
                imageDimensions = new Dimension(mvs.applyTo(width), mvs.applyTo(height));
345
            }
346
        }
347
        return imageDimensions;
348
    }
349

  
350
    static private boolean isUndefined(Integer val) {
351
        return val == null || val == 0;
352
    }
353

  
354
    /**
355
     * Strategies for replacing <code>null</code> values with a numeric value.
356
     *
357
     * @author a.kohlbecker
358
     */
359
    public enum MissingValueStrategy {
360
        /**
361
         * replace <code>null</code> by {@link Integer#MAX_VALUE}
362
         */
363
        MAX(Integer.MAX_VALUE),
364
        /**
365
         * replace <code>null</code> by <code>0</code>
366
         */
367
        ZERO(0);
368

  
369
        private Integer defaultValue;
370

  
371
        MissingValueStrategy(Integer defaultValue){
372
            this.defaultValue = defaultValue;
373
        }
374

  
375
        public Integer applyTo(Integer val){
376
            if(val == null){
377
                return defaultValue;
378
            } else {
379
                return val;
380
            }
381
        }
382
    }
383
  }

Also available in: Unified diff