text:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<title>Guards (Dollar 0.4.5195 API)</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="date" content="2017-09-28">
<link rel="stylesheet" type="text/css" href="../../../stylesheet.css" title="Style">
<link rel="stylesheet" type="text/css" href="../../../jquery/jquery-ui.css" title="Style">

JavaScript is disabled on your browser.

Skip navigation links

Overview

Package

Class

Use

Tree

Deprecated

Index

Help

Prev Class

Next Class

Frames

No Frames

All Classes

SEARCH:
<input type="text" id="search" value=" " disabled="disabled">
<input type="reset" id="reset" value=" " disabled="disabled">

JavaScript is disabled on your browser.

Summary:

Field |

Required |

Optional

Detail:

Field |

Element

Package dollar.api.guard

Annotation Type Guards

@Retention(RUNTIME)
@Target(METHOD)
public @interface Guards

Required Element Summary

Required Elements

Modifier and Type
Required Element
Description

@NotNull Guarded[]
value

Element Detail

value

@NotNull
@NotNull Guarded[] value

Skip navigation links

Overview

Package

Class

Use

Tree

Deprecated

Index

Help

Prev Class

Next Class

Frames

No Frames

All Classes

JavaScript is disabled on your browser.

Summary:

Field |

Required |

Optional

Detail:

Field |

Element

Copyright © 2017. All rights reserved.


text:
"""
make .pyc files beside the rrlog .py files immediately
"""

if __name__ == "__main__":
	import compileall
	compileall.compile_dir("./lib", force=True)

redpajama_set_name: RedPajamaGithub


text:
<ivysettings>
	<settings defaultResolver="default-resolver" />

	<include url="${ivy.default.settings.dir}/ivysettings-public.xml" />
	<include url="${ivy.default.settings.dir}/ivysettings-shared.xml" />
	<include url="${ivy.default.settings.dir}/ivysettings-local.xml" />
	<include url="${ivy.default.settings.dir}/ivysettings-main-chain.xml" />
	<include url="${ivy.default.settings.dir}/ivysettings-default-chain.xml" />

	<resolvers>
		<url name="jboss" m2compatible="true">
			<artifact pattern="http://repository.jboss.org/maven2/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
		</url>

		<chain name="default-resolver" returnFirst="true">
			<resolver ref="local" />
			<resolver ref="public" />
			<resolver ref="jboss" />
		</chain>
	</resolvers>

</ivysettings>


text:
(function () {
  'use strict';

  angular
    .module('loopbackAdmin.core')
    .config(routeConfig);

  /** @ngInject */
  function routeConfig($stateProvider) {
    $stateProvider
      .state('app', {
        url: '/app',
        templateUrl: 'app/components/core/structure/app.structure.html',
        controller: 'AppController',
        controllerAs: 'app',
        title: 'loopbackAdmin'
      });
  }

})();


text:
module Census
  module Shoulda

    def should_accept_nested_attributes_for(*attr_names)
      klass = self.name.gsub(/Test$/, '').constantize

      context "#{klass}" do
        attr_names.each do |association_name|
          should "accept nested attrs for #{association_name}" do
            meth = "#{association_name}_attributes="
            assert  ([meth,meth.to_sym].any?{ |m| klass.instance_methods.include?(m) }),
                    "#{klass} does not accept nested attributes for #{association_name}"
          end
        end
      end
    end

    def should_act_as_list
      klass = self.name.gsub(/Test$/, '').constantize

      context "To support acts_as_list" do
        should_have_db_column('position', :type => :integer)
      end

      should "include ActsAsList methods" do
        assert klass.include?(ActsAsList::InstanceMethods)
      end

      should_have_instance_methods :acts_as_list_class, :position_column, :scope_condition
    end

  end
end

Test::Unit::TestCase.extend(Census::Shoulda)


text:
/**
 * Created by Yang on 17/11/16.
 */

import styled from 'styled-components';
const Label = styled.label`
  &&{
    color:#666; font-size:94%; font-weight:500;
  }
`;

export default Label;


text:
title: Monkey
playwright: Jake Levy and Libby Horobin
student_written: true
period: Spring
season: Online
season_sort: 340
date_start: 2020-08-05
date_end: 2020-08-05
venue: YouTube
venue_sort: Online

cast:
- role: Des
  name: Reilly Salmon
- role: Danny
  name: Charlie Bellwood
- role: Sean
  name: Joe Butler
- role: Ava
  name: Eloise Dooley
- role: Nicky
  name: Oliver Binns

crew:
- role: Director
  name: Jake Levy
- role: Shadow Director
  name: Rachel Coussins
- role: Producer
  name: Libby Horobin
- role: Editor
  name: Caetano Capurro
- role: Editor
  name: Charlotte Smith
- role: Publicity Assistant
  name: Skylar Turnbull Hurd
- role: Poster Designer
  name: Skylar Turnbull Hurd
- role: Camera Operator
  name: Josh Spear
- role: Captioner
  name: Zoe Smith
- role: Captioner
  name: Cecilia Alexander

assets:
  - type: poster
    image: qf6qBtL

links:
  - type: YouTube
    title: Watch 'Monkey' on YouTube
    href: https://www.youtube.com/watch?v=n6hvRj968I8

---

A Zoom pub quiz, held to satisfy the desire for human interaction, ends in a harsh reality check for a friendship group challenged by addiction. A distrustful Des quietly observes the call, whilst analysing his friends' unfiltered motives and mannerisms. His suspicions regarding Danny's escalating addiction to drugs bubble under the surface as he navigates the power dynamics underpinning his friendships. Des is confronted by a dilemma between his own moral compass and the realities of living closely with an addict.


text:
module Clearance
  module Testing
    module Matchers
      def deny_access(opts = {})
        DenyAccessMatcher.new(self, opts)
      end

      class DenyAccessMatcher
        attr_reader :failure_message, :negative_failure_message

        def initialize(context, opts)
          @context = context
          @flash = opts[:flash]
          @url = opts[:redirect]

          @failure_message = ''
          @negative_failure_message = ''
        end

        def description
          'deny access'
        end

        def matches?(controller)
          @controller = controller
          sets_the_flash? && redirects_to_url?
        end

        private

        def denied_access_url
          if @controller.signed_in?
            '/'
          else
            @controller.sign_in_url
          end
        end

        def flash_notice
          @controller.flash[:notice]
        end

        def flash_notice_value
          if flash_notice.respond_to?(:values)
            flash_notice.values.first
          else
            flash_notice
          end
        end

        def redirects_to_url?
          @url ||= denied_access_url

          begin
            @context.send(:assert_redirected_to, @url)
            @negative_failure_message << "Didn't expect to redirect to #{@url}."
            true
          rescue Clearance::Testing::AssertionError
            @failure_message << "Expected to redirect to #{@url} but did not."
            false
          end
        end

        def sets_the_flash?
          if @flash.blank?
            true
          else
            if flash_notice_value == @flash
              @negative_failure_message << "Didn't expect to set the flash to #{@flash}"
              true
            else
              @failure_message << "Expected the flash to be set to #{@flash} but was #{flash_notice_value}"
              false
            end
          end
        end
      end
    end
  end
end


text:
//
//  HQAlterView.h
//  CoreAnimation
//
//  Created by apple on 16/2/17.
//  Copyright © 2016年 HQ. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface HQAlterView : UIView
typedef void(^HQOkBlock)(void);
typedef void(^HQCancelBlock)(void);

/**
 *  只显示message,有文字类型取消和确认按钮
 *
 *  @param message     message description
 *  @param cancelText  cancelText description
 *  @param okText      okText description
 *  @param cancelBlock cancelBlock description
 *  @param okBlock     okBlock description
 *
 *  @return return value description
 */
-(instancetype)initWithMessage:(NSString*)message cancelText:(NSString*)cancelText okText:(NSString*)okText cancelBlock:(HQCancelBlock)cancelBlock okBlock:(HQOkBlock)okBlock;

/**
 *  只显示message,只有文字类型确认按钮
 *
 *  @param message message description
 *  @param okText  okText description
 *  @param okBlock okBlock description
 *
 *  @return return value description
 */
-(instancetype)initWithMessage:(NSString*)message okText:(NSString*)okText okBlock:(HQOkBlock)okBlock;

/**
 *  显示title和message,为nil则不显示,只有文字类型确认按钮
 *
 *  @param title   title description
 *  @param message message description
 *  @param okText  okText description
 *  @param okBlock okBlock description
 *
 *  @return return value description
 */
-(instancetype)initWithTitle:(NSString*)title message:(NSString*)message okText:(NSString*)okText okBlock:(HQOkBlock)okBlock;

/**
 *  显示title和message,为nil则不显示,有文字类型取消和确认按钮
 *
 *  @param title       title description
 *  @param message     message description
 *  @param cancelText  cancelText description
 *  @param okText      okText description
 *  @param cancelBlock cancelBlock description
 *  @param okBlock     okBlock description
 *
 *  @return return value description
 */
-(instancetype)initWithTitle:(NSString*)title message:(NSString*)message cancelText:(NSString*)cancelText okText:(NSString*)okText cancelBlock:(HQCancelBlock)cancelBlock okBlock:(HQOkBlock)okBlock;

//设置自定义的button
/**
 *  只显示message,有自定义取消和确认按钮
 *
 *  @param message      message description
 *  @param cancelButton cancelButton description
 *  @param okButton     okButton description
 *  @param cancelBlock  cancelBlock description
 *  @param okBlock      okBlock description
 *
 *  @return return value description
 */
-(instancetype)initWithMessage:(NSString*)message cancelButton:(UIButton*)cancelButton okButton:(UIButton*)okButton cancelBlock:(HQCancelBlock)cancelBlock okBlock:(HQOkBlock)okBlock;

/**
 *  只显示message,自定义确认按钮
 *
 *  @param message  message description
 *  @param okButton okButton description
 *  @param okBlock  okBlock description
 *
 *  @return return value description
 */
-(instancetype)initWithMessage:(NSString*)message okButton:(UIButton*)okButton okBlock:(HQOkBlock)okBlock;

/**
 *  显示title和message,为nil不显示,自定义确认按钮
 *
 *  @param title    title description
 *  @param message  message description
 *  @param okButton okButton description
 *  @param okBlock  okBlock description
 *
 *  @return return value description
 */
-(instancetype)initWithTitle:(NSString*)title message:(NSString*)message okButton:(UIButton*)okButton okBlock:(HQOkBlock)okBlock;

/**
 *  显示title和message,为nil不显示,自定义取消和确认按钮
 *
 *  @param title        title description
 *  @param message      message description
 *  @param cancelButton cancelButton description
 *  @param okButton     okButton description
 *  @param cancelBlock  cancelBlock description
 *  @param okBlock      okBlock description
 *
 *  @return return value description
 */
-(instancetype)initWithTitle:(NSString*)title message:(NSString*)message cancelButton:(UIButton*)cancelButton okButton:(UIButton*)okButton cancelBlock:(HQCancelBlock)cancelBlock okBlock:(HQOkBlock)okBlock;

/**
 *  设置message对齐方式
 *
 *  @param alignment alignment description
 */
-(void)setMessageAlignment:(NSTextAlignment)alignment;

/**
 *  将取消按钮字体颜色设置为不显眼
 */
-(void)setCancelButtonTextColorToLow;

/**
 *  显示HQAlertView
 */
-(void)show;

@end


text:
import math
import time

from monotonic import monotonic as _now

class SpeedLimit(object):
    """Speed/limiting iterator wrapper object.

    A wrapper object that uses the `token bucket`_ algorithm to limit the
    rate at which values comes out of an iterable. This can be used to limit
    the consumption speed of iteration of some other iterator (or iterable).

    .. _token bucket: http://en.wikipedia.org/wiki/Token_bucket
    """
    def __init__(self,
                 # How many items to yield from the provided
                 # wrapped iterator (per second).
                 items_per_second,
                 # Used to simulate a thread with its own 'tic rate'. Making
                 # this smaller affects the accuracy of the 'tic' calculation,
                 # which affects the accuracy of consumption (and delays).
                 refresh_rate_seconds=0.01,
                 # How *full* the initial bucket is.
                 initial_bucket_size=1,
                 # Made a keyword argument, so one could replace this
                 # with a eventlet.sleep or other idling function...
                 sleep_func=time.sleep):
        self._refresh_rate_seconds = refresh_rate_seconds
        self._bucket = (items_per_second *
                        refresh_rate_seconds * initial_bucket_size)
        self._items_per_tic = items_per_second * refresh_rate_seconds
        self._next_fill = _now() + refresh_rate_seconds
        self._sleep = sleep_func

    def _check_fill(self):
        # Fill the bucket based on elapsed time.
        #
        # This simulates a background thread...
        now = _now()
        if now > self._next_fill:
            d = now - self._next_fill
            tics = int(math.ceil(d / self._refresh_rate_seconds))
            self._bucket += tics * self._items_per_tic
            self._next_fill += tics * self._refresh_rate_seconds

    def speed_limit_iter(self, itr, chunk_size_cb=None):
        """Return an iterator/generator which limits after each iteration.

        :param itr: an iterator to wrap
        :param chunk_size_cb: a function that can calculate the
                              size of each chunk (if none provided this
                              defaults to 1)
        """
        for chunk in itr:
            if chunk_size_cb is None:
                sz = 1
            else:
                sz = chunk_size_cb(chunk)
            self._check_fill()
            if sz > self._bucket:
                now = _now()
                tics = int((sz - self._bucket) / self._items_per_tic)
                tm_diff = self._next_fill - now
                secs = tics * self._refresh_rate_seconds
                if tm_diff > 0:
                    secs += tm_diff
                self._sleep(secs)
                self._check_fill()
            self._bucket -= sz
            yield chunk


text:
package org.elasticsearch.plugin.nlpcn;

import com.google.common.collect.ImmutableMap;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;

import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestChannel;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.internal.InternalSearchHit;
import org.elasticsearch.search.internal.InternalSearchHits;
import org.nlpcn.es4sql.domain.Field;
import org.nlpcn.es4sql.exception.SqlParseException;
import org.nlpcn.es4sql.query.SqlElasticRequestBuilder;
import org.nlpcn.es4sql.query.join.HashJoinElasticRequestBuilder;
import org.nlpcn.es4sql.query.join.JoinRequestBuilder;
import org.nlpcn.es4sql.query.join.NestedLoopsElasticRequestBuilder;
import org.nlpcn.es4sql.query.join.TableInJoinRequestBuilder;

import java.io.IOException;
import java.util.*;

/**
 * Created by Eliran on 15/9/2015.
 */
public abstract class ElasticJoinExecutor {
    protected SearchHits results ;
    protected MetaSearchResult metaResults;
    protected final int MAX_RESULTS_ON_ONE_FETCH = 10000;
    private Set<String> aliasesOnReturn;
    private boolean allFieldsReturn;

    protected ElasticJoinExecutor(JoinRequestBuilder requestBuilder) {
        metaResults = new MetaSearchResult();
        aliasesOnReturn = new HashSet<>();
        List<Field> firstTableReturnedField = requestBuilder.getFirstTable().getReturnedFields();
        List<Field> secondTableReturnedField = requestBuilder.getSecondTable().getReturnedFields();
        allFieldsReturn = (firstTableReturnedField == null || firstTableReturnedField.size() == 0)
                            && (secondTableReturnedField == null || secondTableReturnedField.size() == 0);
    }

    public void  sendResponse(RestChannel channel){
        try {
            String json = resultAsString();
            BytesRestResponse bytesRestResponse = new BytesRestResponse(RestStatus.OK, json);
            channel.sendResponse(bytesRestResponse);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void run() throws IOException, SqlParseException {
        long timeBefore = System.currentTimeMillis();
        List<InternalSearchHit> combinedSearchHits =  innerRun();
        int resultsSize = combinedSearchHits.size();
        InternalSearchHit[] hits = combinedSearchHits.toArray(new InternalSearchHit[resultsSize]);
        this.results = new InternalSearchHits(hits, resultsSize,1.0f);
        long joinTimeInMilli = System.currentTimeMillis() - timeBefore;
        this.metaResults.setTookImMilli(joinTimeInMilli);
    }

    //use our deserializer instead of results toXcontent because the source field is differnet from sourceAsMap.
    public String resultAsString() throws IOException {
        if(this.results == null) return null;
        Object[] searchHits;
        searchHits = new Object[(int) this.results.totalHits()];
        int i = 0;
        for(SearchHit hit : this.results) {
            HashMap<String,Object> value = new HashMap<>();
            value.put("_id",hit.getId());
            value.put("_type", hit.getType());
            value.put("_score", hit.score());
            value.put("_source", hit.sourceAsMap());
            searchHits[i] = value;
            i++;
        }
        HashMap<String,Object> hits = new HashMap<>();
        hits.put("total",this.results.totalHits());
        hits.put("max_score",this.results.maxScore());
        hits.put("hits",searchHits);
        XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON).prettyPrint();
        builder.startObject();
        builder.field("took", metaResults.getTookImMilli());
        builder.field("timed_out",metaResults.isTimedOut());
        builder.field("_shards", ImmutableMap.of("total", metaResults.getTotalNumOfShards(),
                "successful", metaResults.getSuccessfulShards()
                , "failed", metaResults.getFailedShards()));
        builder.field("hits",hits) ;
        builder.endObject();

        return builder.string();
    }

    protected abstract List<InternalSearchHit> innerRun() throws IOException, SqlParseException ;

    public SearchHits getHits(){
        return results;
    }

    public static ElasticJoinExecutor createJoinExecutor(Client client, SqlElasticRequestBuilder requestBuilder){
        if(requestBuilder instanceof HashJoinElasticRequestBuilder) {
            HashJoinElasticRequestBuilder hashJoin = (HashJoinElasticRequestBuilder) requestBuilder;
            return new HashJoinElasticExecutor(client, hashJoin);
        }
        else if (requestBuilder instanceof NestedLoopsElasticRequestBuilder){
            NestedLoopsElasticRequestBuilder nestedLoops = (NestedLoopsElasticRequestBuilder) requestBuilder;
            return  new NestedLoopsElasticExecutor(client,nestedLoops);
        }
        else {
            throw new RuntimeException("Unsuported requestBuilder of type: " + requestBuilder.getClass());
        }
    }

    protected void mergeSourceAndAddAliases(Map<String,Object> secondTableHitSource, InternalSearchHit searchHit,String t1Alias,String t2Alias) {
        Map<String,Object> results = mapWithAliases(searchHit.getSource(), t1Alias);
        results.putAll(mapWithAliases(secondTableHitSource, t2Alias));
        searchHit.getSource().clear();
        searchHit.getSource().putAll(results);
    }

    protected Map<String,Object> mapWithAliases(Map<String, Object> source, String alias) {
        Map<String,Object> mapWithAliases = new HashMap<>();
        for(Map.Entry<String,Object> fieldNameToValue : source.entrySet()) {
            if(!aliasesOnReturn.contains(fieldNameToValue.getKey()))
                mapWithAliases.put(alias + "." + fieldNameToValue.getKey(), fieldNameToValue.getValue());
            else mapWithAliases.put(fieldNameToValue.getKey(),fieldNameToValue.getValue());
        }
        return mapWithAliases;
    }

    protected void  onlyReturnedFields(Map<String, Object> fieldsMap, List<Field> required,boolean allRequired) {
        HashMap<String,Object> filteredMap = new HashMap<>();
        if(allFieldsReturn || allRequired) {
            filteredMap.putAll(fieldsMap);
            return;
        }
        for(Field field: required){
            String name = field.getName();
            String returnName = name;
            String alias = field.getAlias();
            if(alias !=null && alias !=""){
                returnName = alias;
                aliasesOnReturn.add(alias);
            }
            filteredMap.put(returnName, deepSearchInMap(fieldsMap, name));
        }
        fieldsMap.clear();
        fieldsMap.putAll(filteredMap);

    }

    protected Object deepSearchInMap(Map<String, Object> fieldsMap, String name) {
        if(name.contains(".")){
            String[] path = name.split("\\.");
            Map<String,Object> currentObject = fieldsMap;
            for(int i=0;i<path.length-1 ;i++){
                Object valueFromCurrentMap = currentObject.get(path[i]);
                if(valueFromCurrentMap == null) return null;
                if(!Map.class.isAssignableFrom(valueFromCurrentMap.getClass())) return null;
                currentObject = (Map<String, Object>) valueFromCurrentMap;
            }
            return currentObject.get(path[path.length-1]);
        }

        return fieldsMap.get(name);
    }

    protected void addUnmatchedResults(List<InternalSearchHit> combinedResults, Collection<SearchHitsResult> firstTableSearchHits, List<Field> secondTableReturnedFields,int currentNumOfIds, int totalLimit,String t1Alias,String t2Alias) {
        boolean limitReached = false;
        for(SearchHitsResult hitsResult : firstTableSearchHits){
            if(!hitsResult.isMatchedWithOtherTable())
                for (InternalSearchHit hit : hitsResult.getSearchHits()) {

                    //todo: decide which id to put or type. or maby its ok this way. just need to doc.
                    InternalSearchHit unmachedResult = createUnmachedResult(secondTableReturnedFields, hit.docId(), t1Alias, t2Alias, hit);
                    combinedResults.add(unmachedResult);
                    currentNumOfIds++;
                    if (currentNumOfIds >= totalLimit) {
                        limitReached = true;
                        break;
                    }

                }
            if(limitReached) break;
        }
    }

    protected InternalSearchHit createUnmachedResult( List<Field> secondTableReturnedFields, int docId, String t1Alias, String t2Alias, SearchHit hit) {
        String unmatchedId = hit.id() + "|0";
        Text unamatchedType = new Text(hit.getType() + "|null");

        InternalSearchHit searchHit = new InternalSearchHit(docId, unmatchedId, unamatchedType, hit.getFields());

        searchHit.sourceRef(hit.getSourceRef());
        searchHit.sourceAsMap().clear();
        searchHit.sourceAsMap().putAll(hit.sourceAsMap());
        Map<String,Object> emptySecondTableHitSource = createNullsSource(secondTableReturnedFields);

        mergeSourceAndAddAliases(emptySecondTableHitSource, searchHit,t1Alias,t2Alias);

        return searchHit;
    }

    protected Map<String, Object> createNullsSource(List<Field> secondTableReturnedFields) {
        Map<String,Object> nulledSource = new HashMap<>();
        for(Field field : secondTableReturnedFields){
            if(!field.getName().equals("*")){
                nulledSource.put(field.getName(),null);
            }
        }
        return nulledSource;
    }

    protected void updateMetaSearchResults( SearchResponse searchResponse) {
        this.metaResults.addSuccessfulShards(searchResponse.getSuccessfulShards());
        this.metaResults.addFailedShards(searchResponse.getFailedShards());
        this.metaResults.addTotalNumOfShards(searchResponse.getTotalShards());
        this.metaResults.updateTimeOut(searchResponse.isTimedOut());
    }

    protected SearchResponse scrollOneTimeWithMax(Client client,TableInJoinRequestBuilder tableRequest) {
        SearchResponse responseWithHits;SearchRequestBuilder scrollRequest = tableRequest.getRequestBuilder()
                .setScroll(new TimeValue(60000))
                .setSize(MAX_RESULTS_ON_ONE_FETCH);
        boolean ordered = tableRequest.getOriginalSelect().isOrderdSelect();
        if(!ordered) scrollRequest.setSearchType(SearchType.SCAN);
        responseWithHits = scrollRequest.get();
        //on ordered select - not using SCAN , elastic returns hits on first scroll
        if(!ordered)
            responseWithHits = client.prepareSearchScroll(responseWithHits.getScrollId()).setScroll(new TimeValue(600000)).get();
        return responseWithHits;
    }

}


text:
package com.android.statementservice.retriever;

import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

/**
 * Utility library for computing certificate fingerprints. Also includes fields name used by
 * Statement JSON string.
 */
public final class Utils {

    private Utils() {}

    /**
     * Field name for namespace.
     */
    public static final String NAMESPACE_FIELD = "namespace";

    /**
     * Supported asset namespaces.
     */
    public static final String NAMESPACE_WEB = "web";
    public static final String NAMESPACE_ANDROID_APP = "android_app";

    /**
     * Field names in a web asset descriptor.
     */
    public static final String WEB_ASSET_FIELD_SITE = "site";

    /**
     * Field names in a Android app asset descriptor.
     */
    public static final String ANDROID_APP_ASSET_FIELD_PACKAGE_NAME = "package_name";
    public static final String ANDROID_APP_ASSET_FIELD_CERT_FPS = "sha256_cert_fingerprints";

    /**
     * Field names in a statement.
     */
    public static final String ASSET_DESCRIPTOR_FIELD_RELATION = "relation";
    public static final String ASSET_DESCRIPTOR_FIELD_TARGET = "target";
    public static final String DELEGATE_FIELD_DELEGATE = "include";

    private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
            'A', 'B', 'C', 'D', 'E', 'F' };

    /**
     * Joins a list of strings, by placing separator between each string. For example,
     * {@code joinStrings("; ", Arrays.asList(new String[]{"a", "b", "c"}))} returns
     * "{@code a; b; c}".
     */
    public static String joinStrings(String separator, List<String> strings) {
        switch(strings.size()) {
            case 0:
                return "";
            case 1:
                return strings.get(0);
            default:
                StringBuilder joiner = new StringBuilder();
                boolean first = true;
                for (String field : strings) {
                    if (first) {
                        first = false;
                    } else {
                        joiner.append(separator);
                    }
                    joiner.append(field);
                }
                return joiner.toString();
        }
    }

    /**
     * Returns the normalized sha-256 fingerprints of a given package according to the Android
     * package manager.
     */
    public static List<String> getCertFingerprintsFromPackageManager(String packageName,
            Context context) throws NameNotFoundException {
        Signature[] signatures = context.getPackageManager().getPackageInfo(packageName,
                PackageManager.GET_SIGNATURES).signatures;
        ArrayList<String> result = new ArrayList<String>(signatures.length);
        for (Signature sig : signatures) {
            result.add(computeNormalizedSha256Fingerprint(sig.toByteArray()));
        }
        return result;
    }

    /**
     * Computes the hash of the byte array using the specified algorithm, returning a hex string
     * with a colon between each byte.
     */
    public static String computeNormalizedSha256Fingerprint(byte[] signature) {
        MessageDigest digester;
        try {
            digester = MessageDigest.getInstance("SHA-256");
        } catch (NoSuchAlgorithmException e) {
            throw new AssertionError("No SHA-256 implementation found.");
        }
        digester.update(signature);
        return byteArrayToHexString(digester.digest());
    }

    /**
     * Returns true if there is at least one common string between the two lists of string.
     */
    public static boolean hasCommonString(List<String> list1, List<String> list2) {
        HashSet<String> set2 = new HashSet<>(list2);
        for (String string : list1) {
            if (set2.contains(string)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Converts the byte array to an lowercase hexadecimal digits String with a colon character (:)
     * between each byte.
     */
    private static String byteArrayToHexString(byte[] array) {
        if (array.length == 0) {
          return "";
        }
        char[] buf = new char[array.length * 3 - 1];

        int bufIndex = 0;
        for (int i = 0; i < array.length; i++) {
            byte b = array[i];
            if (i > 0) {
                buf[bufIndex++] = ':';
            }
            buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F];
            buf[bufIndex++] = HEX_DIGITS[b & 0x0F];
        }
        return new String(buf);
    }
}