All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fm.common.IP.scala Maven / Gradle / Ivy

/*
 * Copyright 2014 Frugal Mechanic (http://frugalmechanic.com)
 *
 * 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 fm.common

import java.net.InetAddress
import scala.util.matching.Regex

final case class InvalidIPException(msg: String) extends IllegalArgumentException(msg)

/**
 * Helpers for parsing and working with IPv4 addresses
 */
object IP {
  val MAX_IP: Long = 4294967295L

  val empty: IP = new IP(0)
  
  /**
   * Is this a valid IPv4 Address formatted as xxx.xxx.xxx.xxx ?
   * NOTE!: This doesn't mean the apply() method will fail since it does hostname resolution.
   */
  def isValid(ip: String): Boolean = try { toInt(ip); true } catch { case _: InvalidIPException => false }

  // ddd.ddd.ddd.ddd with an optional trailing dot followed by the end of the string/line or whitespace
  private def ipv4Pattern: Regex = """(^|\s|,)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\.?($|(?=(\s|,)))""".r
  
  def findAllIPsIn(ips: String): IndexedSeq[IP] = if(null == ips) Vector.empty else ipv4Pattern.findAllIn(ips).matchData.map{_.group(2)}.map{apply}.toIndexedSeq
  
  object parse {
    def apply(ip: String): Option[IP] = get(ip)
    def unapply(ip: String): Option[IP] = get(ip)
  }
  
  def get(ip: String): Option[IP] = try{ Some(apply(ip)) } catch{ case _: InvalidIPException => None }
  
  def apply(ip: String): IP = {
    if (ip.isNullOrBlank) throw new InvalidIPException("IP Address cannot be empty")
    
    val dotCount: Int = ip.count{ _ === '.' }
    
    try {
      // Allow 1.2.3.4 and 1.2.3.4. (trailing dot)
      if (dotCount === 3 || dotCount === 4) apply(toInt(ip))
      else if (ip.forall{Character.isDigit(_)}) apply(ip.toLong)
      else throw new InvalidIPException("Not sure how to parse: "+ip)
    } catch {
      case ex: NumberFormatException => try { apply(InetAddress.getByName(ip)) } catch { case _: NoSuchElementException => throw new InvalidIPException("Not sure how to parse: "+ip) }
    }
  }
  
  def apply(ip: Long): IP = apply(toInt(ip))
  def apply(address: InetAddress): IP = apply(toInt(address.getAddress))
  def apply(bytes: Array[Byte]): IP = apply(toInt(bytes))

  /**
   * The single apply() method that actually creates an IP object
   */
  def apply(ip: Int): IP = new IP(ip)

  def toInt(ip: String): Int = {
    val fixedIp: String = if (ip.endsWith(".")) ip.substring(0, ip.length - 1) else ip
    val octets = fixedIp.trim.split('.').map{ (s: String) => s.toShortOptionCached.filter{ (n: Short) => n >= 0 && n <= 255 }.getOrElse{ throw InvalidIPException("Invalid IP Address: "+fixedIp) }.toByte }
    if (octets.size =!= 4) throw InvalidIPException("Invalid IP Address: "+fixedIp)
    toInt(octets)
  }

  def toLong(ip: String): Long = toLong(toInt(ip))

  def toInt(bytes: Array[Byte]): Int = {
    if (bytes.size =!= 4) throw InvalidIPException("Invalid IP Address: "+bytes.toSeq)
    ((bytes(0) & 0xff) << 24) + ((bytes(1) & 0xff) << 16) + ((bytes(2) & 0xff) << 8) + (bytes(3) & 0xff)
  }

  def toInt(ints: Array[Int]): Int = toInt(ints.map{_.toByte})

  def toLong(bytes: Array[Byte]): Long = toLong(toInt(bytes))
  def toLong(ints: Array[Int]): Long = toLong(toInt(ints))

  def toBytes(ip: Int): Array[Byte] = {
    val bytes = new Array[Byte](4)
    bytes(0) = ((ip >>> 24) & 0xff).toByte
    bytes(1) = ((ip >>> 16) & 0xff).toByte
    bytes(2) = ((ip >>> 8) & 0xff).toByte
    bytes(3) = (ip & 0xff).toByte
    bytes
  }

  def toBytes(ip: Long): Array[Byte] = toBytes(toInt(ip))

  def toLong(ip: Int): Long = ip & 0xffffffffL
  def toInt(ip: Long): Int = ip.toInt

  def toIntArray(ip: Int): Array[Int] = toBytes(ip).map{_ & 0xff}
  def toIntArray(ip: Long): Array[Int] = toBytes(ip).map{_ & 0xff}

  def toString(ip: Int): String = toIntArray(ip).mkString(".")
  def toString(ip: Long): String = toIntArray(ip).mkString(".")

  def toReversedString(ip: String): String = toReversedString(toInt(ip))

  def toReversedString(ip: Int): String = toIntArray(ip).reverse.mkString(".")
  def toReversedString(ip: Long): String = toIntArray(ip).reverse.mkString(".")
}

/**
 * Simple Wrapper around an IPv4 Address
 */
final class IP private(val ip: Int) extends AnyVal with Ordered[IP] with IPOrSubnet {
  import IP._
  
  // IPOrSubnet implementation
  def start: IP = this
  
  // IPOrSubnet implementation
  def end: IP = this
  
  // IPOrSubnet implementation
  def mask: Int = -1 // all 1's
  
  // IPOrSubnet implementation
  def toIPSubnet: IPSubnet = IPSubnet(this, 32)
  
  def bytes: Array[Byte] = toBytes(ip)
  def intArray: Array[Int] = toIntArray(ip)
  def inetAddress: InetAddress = InetAddress.getByAddress(bytes)

  def octets: (Int, Int, Int, Int) = {
    val arr: Array[Int] = intArray
    (arr(0), arr(1), arr(2), arr(3))
  }
  
  def longValue: Long = toLong(ip)
  def intValue: Int = ip
  
  /** Is this a 127.0.0.0/8 IP Address? */
  def isLocalhost: Boolean = IPSubnet.Localhost.contains(this)
  
  /** Is this NOT a 127.0.0.0/8 IP Address? */
  def isNotLocalhost: Boolean = !isLocalhost
  
  /** Is this an RFC 1918 Private IP Address (or Localhost)? */
  def isPrivate: Boolean = IPSubnets.Private.contains(this)

  def reversedString: String = toReversedString(ip)
  override def toString: String = IP.toString(ip)

  // Ordered[IP] Implementation
  def compare(that: IP): Int = longValue.compare(that.longValue)
  
  def contains(other: IP): Boolean = ip === other.ip
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy